diff --git a/README.md b/README.md index d0671b0..0defdfa 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,9 @@ This is the available options: // Allows comment extraction. Accepts either a function or array onComment: [] + // Allows detection of automatic semicolon insertion. Accepts a callback function that will be passed the charater offset where the semicolon was inserted + onInsertedSemicolon: (pos) => {} + // Allows token extraction. Accepts either a function or array onToken: [] @@ -115,13 +118,20 @@ If an array is supplied, comments/tokens will be pushed to the array, the item i If a function callback is supplied, the signature must be ```ts -function onComment(type: string, value: string, start: number, end: number, loc: SourceLocation): void {} +declare function onComment(type: string, value: string, start: number, end: number, loc: SourceLocation): void; -function onToken(token: string, start: number, end: number, loc: SourceLocation): void {} +declare function onToken(token: string, start: number, end: number, loc: SourceLocation): void; ``` Note the `start/end/loc` information are provided to the function callback regardless of the settings on ranges and loc flags. onComment callback has one extra argument `value: string` for the body string of the comment. +### onInsertedSemicolon +If a function callback is supplied, the signature must be + +```ts +declare function onInsertedSemicolon(position: number): void; +``` + ## Example usage ```js @@ -184,4 +194,4 @@ This will return when serialized in json: } ] } -``` +``` \ No newline at end of file diff --git a/src/common.ts b/src/common.ts index 333bb9d..2c62466 100644 --- a/src/common.ts +++ b/src/common.ts @@ -173,6 +173,11 @@ export const enum ScopeKind { */ export type OnComment = void | Comment[] | ((type: string, value: string, start: number, end: number, loc: SourceLocation) => any); +/** + * The type of the `onInsertedSemicolon` option. + */ +export type OnInsertedSemicolon = void | ((pos: number) => any); + /** * The type of the `onToken` option. */ @@ -214,6 +219,7 @@ export interface ParserState { end: number; token: Token; onComment: any; + onInsertedSemicolon: any; onToken: any; tokenValue: any; tokenRaw: string; @@ -247,7 +253,11 @@ export function matchOrInsertSemicolon(parser: ParserState, context: Context, sp ) { report(parser, Errors.UnexpectedToken, KeywordDescTable[parser.token & Token.Type]); } - consumeOpt(parser, context, Token.Semicolon); + + if (!consumeOpt(parser, context, Token.Semicolon)) { + // Automatic semicolon insertion has occurred + parser.onInsertedSemicolon?.(parser.startPos); + } } export function isValidStrictMode(parser: ParserState, index: number, tokenPos: number, tokenValue: string): 0 | 1 { diff --git a/src/parser.ts b/src/parser.ts index fee4d95..9e0d3cb 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -13,6 +13,7 @@ import { consume, Flags, OnComment, + OnInsertedSemicolon, OnToken, pushComment, pushToken, @@ -55,7 +56,8 @@ export function create( source: string, sourceFile: string | void, onComment: OnComment | void, - onToken: OnToken | void + onToken: OnToken | void, + onInsertedSemicolon: OnInsertedSemicolon | void ): ParserState { return { /** @@ -180,6 +182,11 @@ export function create( */ onToken, + /** + * Function invoked with the character offset when automatic semicolon insertion occurs + */ + onInsertedSemicolon, + /** * Holds leading decorators before "export" or "class" keywords */ @@ -221,9 +228,11 @@ export interface Options { jsx?: boolean; // Allow edge cases that deviate from the spec specDeviation?: boolean; - // Allows comment extraction. Accepts either a a callback function or an array + // Allows comment extraction. Accepts either a callback function or an array onComment?: OnComment; - // Allows token extraction. Accepts either a a callback function or an array + // Allows detection of automatic semicolon insertion. Accepts a callback function that will be passed the charater offset where the semicolon was inserted + onInsertedSemicolon?: OnInsertedSemicolon; + // Allows token extraction. Accepts either a callback function or an array onToken?: OnToken; // Creates unique key for in ObjectPattern when key value are same uniqueKeyInPattern?: boolean; @@ -235,6 +244,7 @@ export interface Options { export function parseSource(source: string, options: Options | void, context: Context): ESTree.Program { let sourceFile = ''; let onComment; + let onInsertedSemicolon; let onToken; if (options != null) { if (options.module) context |= Context.Module | Context.Strict; @@ -257,6 +267,7 @@ export function parseSource(source: string, options: Options | void, context: Co if (options.onComment != null) { onComment = Array.isArray(options.onComment) ? pushComment(context, options.onComment) : options.onComment; } + if (options.onInsertedSemicolon != null) onInsertedSemicolon = options.onInsertedSemicolon; // Accepts either a callback function to be invoked or an array to collect tokens if (options.onToken != null) { onToken = Array.isArray(options.onToken) ? pushToken(context, options.onToken) : options.onToken; @@ -264,7 +275,7 @@ export function parseSource(source: string, options: Options | void, context: Co } // Initialize parser state - const parser = create(source, sourceFile, onComment, onToken); + const parser = create(source, sourceFile, onComment, onToken, onInsertedSemicolon); // See: https://github.com/tc39/proposal-hashbang if (context & Context.OptionsNext) skipHashBang(parser); diff --git a/test/parser/miscellaneous/onInsertedSemicolon.ts b/test/parser/miscellaneous/onInsertedSemicolon.ts new file mode 100644 index 0000000..ae75a71 --- /dev/null +++ b/test/parser/miscellaneous/onInsertedSemicolon.ts @@ -0,0 +1,14 @@ +import * as t from 'assert'; +import { parseScript } from '../../../src/meriyah'; + +describe('Miscellaneous - oninsertedSemicolon', () => { + it('invokes the callback when automatic semicolon insertion occurs', () => { + const semicolons: number[] = []; + const lines = ['"use strict"', 'self.a;', 'self.b']; + const input = lines.join('\n'); + parseScript(input, { + onInsertedSemicolon: (pos) => semicolons.push(pos) + }); + t.deepEqual(semicolons, [lines[0].length, input.length]); + }); +});