Skip to content

Commit

Permalink
Merge pull request #252 from dmichon-msft/on-inserted-semicolon
Browse files Browse the repository at this point in the history
feat(parser): add onInsertedSemicolon option
  • Loading branch information
3cp committed Mar 5, 2024
2 parents 755876f + 557a893 commit f7fc112
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 8 deletions.
16 changes: 13 additions & 3 deletions README.md
Expand Up @@ -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: []

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -184,4 +194,4 @@ This will return when serialized in json:
}
]
}
```
```
12 changes: 11 additions & 1 deletion src/common.ts
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -214,6 +219,7 @@ export interface ParserState {
end: number;
token: Token;
onComment: any;
onInsertedSemicolon: any;
onToken: any;
tokenValue: any;
tokenRaw: string;
Expand Down Expand Up @@ -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 {
Expand Down
19 changes: 15 additions & 4 deletions src/parser.ts
Expand Up @@ -13,6 +13,7 @@ import {
consume,
Flags,
OnComment,
OnInsertedSemicolon,
OnToken,
pushComment,
pushToken,
Expand Down Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -257,14 +267,15 @@ 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;
}
}

// 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);
Expand Down
14 changes: 14 additions & 0 deletions 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]);
});
});

0 comments on commit f7fc112

Please sign in to comment.