├── .npmrc
├── _config.yml
├── .prettierignore
├── .mocharc.json
├── prettier.config.js
├── test
├── tsconfig.json
├── parser
│ ├── core.ts
│ ├── miscellaneous
│ │ ├── computed-property-names.ts
│ │ ├── literals.ts
│ │ ├── asi.ts
│ │ ├── lexical.ts
│ │ ├── simple-parameter-list.ts
│ │ ├── trailing.ts
│ │ ├── early-errors.ts
│ │ ├── eval-arguments.ts
│ │ └── annex-b.ts
│ ├── statements
│ │ ├── empty.ts
│ │ ├── with.ts
│ │ └── do-while.ts
│ └── expressions
│ │ ├── object-spread.ts
│ │ ├── logicalAssignment.ts
│ │ └── regexp.ts
├── scanner
│ ├── regexp.ts
│ ├── whitespace.ts
│ ├── bom.ts
│ ├── punctuators.ts
│ ├── comments.ts
│ └── string.ts
└── test262-parser-tests
│ └── parser-tests.ts
├── .gitignore
├── .vscode
├── extensions.json
├── settings.json
└── launch.json
├── .editorconfig
├── scripts
├── project.js
├── list-changed-files.js
├── bump-dev-version.js
├── prettier.js
├── bundle.js
└── generate-unicode.js
├── LICENSE.md
├── src
├── scanner
│ ├── index.ts
│ ├── common.ts
│ ├── tokenization.ts
│ ├── comments.ts
│ ├── template.ts
│ ├── chars.ts
│ ├── regexp.ts
│ ├── identifier.ts
│ ├── string.ts
│ ├── charClassifier.ts
│ └── numeric.ts
├── parser
│ ├── core.ts
│ ├── scope.ts
│ └── declaration.ts
└── seafox.ts
├── tsconfig.json
├── .eslintrc.js
├── .circleci
└── config.yml
├── package.json
└── README.md
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock = true
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | src/token.ts
4 | src/common.ts
5 | src/chars.ts
6 | src/scanner/charClassifier.ts
7 | src/scanner/unicode.ts
8 | package-lock.json
9 |
--------------------------------------------------------------------------------
/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extension": ["ts"],
3 | "color": true,
4 | "reporter": "progress",
5 | "require": ["ts-node/register", "source-map-support/register"],
6 | "recursive": true
7 | }
8 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | semi: true,
5 | singleQuote: true,
6 | tabWidth: 2,
7 | trailingComma: 'none',
8 | useTabs: false,
9 | printWidth: 120
10 | };
11 |
--------------------------------------------------------------------------------
/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["node", "mocha"],
5 | "module": "umd"
6 | },
7 | "include": ["../src", ".", "../test262/run_test262.js"]
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 | node_modules
3 | /coverage
4 | /.nyc_output
5 | /.rpt2_cache
6 | yarn.lock
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | .rollupcache
12 | .fuzz-output
13 | .DS_Store
14 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "christian-kohler.path-intellisense",
4 | "EditorConfig.EditorConfig",
5 | "esbenp.prettier-vscode",
6 | "joelday.docthis",
7 | "steoates.autoimport"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://EditorConfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | indent_style = space
11 | indent_size = 2
12 |
13 | [ts]
14 | indent_size = 2
15 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "./node_modules/typescript/lib",
3 | "tslint.nodePath": "./node_modules/tslint/bin/tslint",
4 | "tslint.exclude": [
5 | "**/node_modules/**",
6 | "**/dist/**",
7 | "**/build/**",
8 | "packages/*/test/**"
9 | ],
10 | "emmet.triggerExpansionOnTab": true,
11 | "search.exclude": {
12 | "**/node_modules": true,
13 | "**/dist": true,
14 | "**/build": true,
15 | "**/.vscode": true
16 | },
17 | "prettier.singleQuote": true,
18 | "deepscan.ignoreConfirmWarning": true
19 | }
20 |
--------------------------------------------------------------------------------
/scripts/project.js:
--------------------------------------------------------------------------------
1 | const { join, resolve } = require('path');
2 | const packageJson = require('../package.json');
3 |
4 | const root = resolve(__dirname, '..');
5 |
6 | module.exports = {
7 | path: root,
8 | pkg: packageJson,
9 | coverage: { path: join(root, 'coverage') },
10 | src: { path: join(root, 'src') },
11 | entry: { path: join(root, 'src', 'seafox.ts') },
12 | dist: { path: join(root, 'dist') },
13 | node_modules: { path: join(root, 'node_modules') },
14 | scripts: { path: join(root, 'scripts') },
15 | test: { path: join(root, 'test') },
16 | 'tsconfig.json': { path: join(root, 'tsconfig.json') },
17 | 'package.json': { path: join(root, 'package.json') },
18 | 'package-lock.json': { path: join(root, 'package-lock.json') }
19 | };
20 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2020 and later, KFlash and others.
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
8 |
--------------------------------------------------------------------------------
/src/scanner/index.ts:
--------------------------------------------------------------------------------
1 | export { CharTypes, CharFlags, isIdentifierPart } from './charClassifier';
2 | export { skipSingleLineComment, skipMultiLineComment, skipSingleHTMLComment } from './comments';
3 | export { scanIdentifierSlowPath, scanIdentifierOrKeyword, scanUnicodeEscapeIdStart } from './identifier';
4 | export { scanStringLiteral } from './string';
5 | export { scanRegularExpression } from './regexp';
6 | export { scanTemplate } from './template';
7 | export { report, Errors } from '../errors';
8 | export { unicodeLookup } from './unicode';
9 | export { fromCodePoint, toHex, readNext, skipMeta } from './common';
10 | export { scanEscapeSequence, Escape, handleStringError } from './string';
11 | export { Chars } from './chars';
12 | export { scanNumber } from './numeric';
13 |
--------------------------------------------------------------------------------
/src/parser/core.ts:
--------------------------------------------------------------------------------
1 | import { Token } from '../token';
2 | import { ParserState, Flags } from './common';
3 |
4 | /**
5 | * Create a new parser instance.
6 | */
7 | export function create(source: string, onToken?: any): ParserState {
8 | return {
9 | source,
10 | flags: Flags.Empty,
11 | index: 0,
12 | start: 0,
13 | endIndex: 0,
14 | lastColumn: 0,
15 | column: 0,
16 | line: 0,
17 | curLine: 1,
18 | offset: 0,
19 | length: source.length,
20 | lastLine: 1,
21 | token: Token.EOF,
22 | newLine: 0,
23 | tokenValue: void 0,
24 | tokenRaw: '',
25 | tokenRegExp: void 0,
26 | onToken: onToken,
27 | lastChar: 0,
28 | assignable: 1,
29 | containsEscapes: 0,
30 | exportedNames: [],
31 | exportedBindings: []
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/test/parser/core.ts:
--------------------------------------------------------------------------------
1 | import * as t from 'assert';
2 | import { parseRoot } from '../../src/seafox';
3 | import { Context } from '../../src/parser/common';
4 |
5 | export const pass = (name: string, valids: [string, Context, any][]) => {
6 | describe(name, () => {
7 | for (const [source, ctx, expected] of valids) {
8 | it(source, () => {
9 | const parser = parseRoot(source, ctx);
10 | t.deepStrictEqual(parser, expected);
11 | });
12 | }
13 | });
14 | };
15 |
16 | export const fail = (name: string, invalid: [string, Context][]) => {
17 | describe(name, () => {
18 | for (const [source, ctx] of invalid) {
19 | it(source, () => {
20 | t.throws(() => {
21 | parseRoot(source, ctx);
22 | });
23 | });
24 | }
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "alwaysStrict": true,
5 | "baseUrl": "./src",
6 | "declaration": true,
7 | "declarationMap": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "importHelpers": true,
10 | "moduleResolution": "node",
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noUnusedParameters": true,
14 | "outDir": "dist",
15 | "preserveConstEnums": false,
16 | "preserveWatchOutput": true,
17 | "pretty": true,
18 | "removeComments": true,
19 | "sourceMap": true,
20 | "strict": true,
21 | "stripInternal": true,
22 | "target":"ES2015",
23 | "typeRoots": ["./node_modules/@types"],
24 | "types": ["node", "mocha"]
25 | },
26 | "include": ["src"]
27 | }
28 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Launch Program",
11 | "program": "${workspaceFolder}\\dist\\umd\\cherow.js",
12 | "preLaunchTask": "tsc: build - tsconfig.json",
13 | "outFiles": [
14 | "${workspaceFolder}/build/**/*.js"
15 | ]
16 | },
17 | {
18 | "type": "node",
19 | "request": "launch",
20 | "name": "Launch mocha tests",
21 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
22 | "args": [
23 | "${workspaceRoot}/test/**/*.ts"
24 | ],
25 | "cwd": "${workspaceRoot}",
26 | "internalConsoleOptions": "openOnSessionStart"
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/scripts/list-changed-files.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 | 'use strict';
8 |
9 | const execFileSync = require('child_process').execFileSync;
10 |
11 | const exec = (command, args) => {
12 | console.log('> ' + [command].concat(args).join(' '));
13 | const options = {
14 | cwd: process.cwd(),
15 | env: process.env,
16 | stdio: 'pipe',
17 | encoding: 'utf-8'
18 | };
19 | return execFileSync(command, args, options);
20 | };
21 |
22 | const execGitCmd = args =>
23 | exec('git', args)
24 | .trim()
25 | .toString()
26 | .split('\n');
27 |
28 | const listChangedFiles = () => {
29 | const mergeBase = execGitCmd(['merge-base', 'HEAD', 'master']);
30 | return new Set([
31 | ...execGitCmd(['diff', '--name-only', '--diff-filter=ACMRTUB', mergeBase]),
32 | ...execGitCmd(['ls-files', '--others', '--exclude-standard'])
33 | ]);
34 | };
35 |
36 | module.exports = listChangedFiles;
37 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | overrides: [
5 | {
6 | files: ['**/*.js'],
7 | extends: ['eslint:recommended', 'plugin:node/recommended'],
8 | parserOptions: { ecmaVersion: 10 }
9 | },
10 | {
11 | files: ['**/*.ts'],
12 | extends: [
13 | 'plugin:@typescript-eslint/eslint-recommended',
14 | 'plugin:@typescript-eslint/recommended',
15 | 'plugin:import/errors',
16 | 'plugin:import/warnings',
17 | 'plugin:import/typescript'
18 | ],
19 | rules: {
20 | '@typescript-eslint/no-use-before-define': [2, { functions: false }], // https://github.com/eslint/eslint/issues/11903
21 | '@typescript-eslint/indent': 0,
22 | 'prefer-const': ['error', { destructuring: 'all' }],
23 |
24 | // TODO: enable it when all problems addressed
25 | '@typescript-eslint/explicit-function-return-type': 0,
26 | '@typescript-eslint/no-explicit-any': 0,
27 | '@typescript-eslint/class-name-casing': 0,
28 | '@typescript-eslint/camelcase': 0
29 | }
30 | },
31 | {
32 | files: ['scripts/*.js'],
33 | rules:
34 | {
35 | "node/no-unsupported-features/es-syntax": ["error", {"version": "10.0", "ignores": []}]
36 | }
37 | }
38 | ]
39 | };
40 |
--------------------------------------------------------------------------------
/test/parser/miscellaneous/computed-property-names.ts:
--------------------------------------------------------------------------------
1 | import * as t from 'assert';
2 | import { parseScript, parseModule } from '../../../src/seafox';
3 |
4 | describe('Miscellaneous - Computed property names', () => {
5 | for (const arg of ['({[1,2]:3})', '({ *a })', '({ *a: 0 })', '({ *[0]: 0 })']) {
6 | it(`${arg}`, () => {
7 | t.throws(() => {
8 | parseScript(`${arg}`, {
9 | disableWebCompat: true
10 | });
11 | });
12 | });
13 |
14 | it(`${arg}`, () => {
15 | t.throws(() => {
16 | parseModule(`${arg}`);
17 | });
18 | });
19 | }
20 |
21 | for (const arg of [
22 | '({"oink"(that, ugly, icefapper) {}})',
23 | '({"moo"() {}})',
24 | '({3() {}})',
25 | '({[6+3]() {}})',
26 | '({get [6+3]() {}, set [5/4](x) {}})',
27 | '({[2*308]:0})',
28 | '({["nUmBeR"+9]:"nein"})',
29 | '({get __proto__() {}, set __proto__(x) {}})',
30 | '({set __proto__(x) {}})',
31 | '({get __proto__() {}})',
32 | '({__proto__:0})',
33 | '({set c(x) {}})',
34 | '({get b() {}})',
35 | '({2e308:0})',
36 | '({0x0:0})'
37 | ]) {
38 | it(`${arg}`, () => {
39 | t.doesNotThrow(() => {
40 | parseScript(`${arg}`);
41 | });
42 | });
43 | it(`${arg}`, () => {
44 | t.doesNotThrow(() => {
45 | parseModule(`${arg}`);
46 | });
47 | });
48 | }
49 | });
50 |
--------------------------------------------------------------------------------
/test/parser/miscellaneous/literals.ts:
--------------------------------------------------------------------------------
1 | import { fail } from '../core';
2 | import { Context } from '../../../src/parser/common';
3 |
4 | fail('Miscellaneous - Literals (fail)', [
5 | [`"use strict"; 'use strict'; ('\\1')`, Context.Empty],
6 | [`"use strict"; 'use strict'; ('\\4')`, Context.Empty],
7 | [`"use strict"; 'use strict'; ('\\11')`, Context.Empty],
8 | [`"use strict"; 'use strict'; ('\\000')`, Context.Empty],
9 | [`"use strict"; 'use strict'; ('\\00')`, Context.Empty],
10 | [`"use strict"; ('\\00n') 'use strict';`, Context.Empty],
11 | [`"use strict"; ('\\00') 'use strict';`, Context.Empty],
12 | [`"use strict"; ('\\000') 'use strict';`, Context.Empty],
13 | [`"use strict"; ('\\4') 'use strict';`, Context.Empty],
14 | [`"use strict"; ('\\1') 'use strict';`, Context.Empty],
15 | [`('\\00n') 'use strict';`, Context.Empty],
16 | [`('\\00') 'use strict';`, Context.Empty],
17 | [`('\\4') 'use strict';`, Context.Empty],
18 | [`('\\1') 'use strict';`, Context.Empty],
19 | [`('\\123') 'use strict';`, Context.Empty],
20 | [`('\\x')`, Context.Empty],
21 | [`('\\x')`, Context.Strict],
22 | [`(")`, Context.Empty],
23 | [`('\\9')`, Context.Empty],
24 | [`('\\9')`, Context.Strict | Context.Module],
25 | [`\\0009`, Context.Empty],
26 | [`("\\u{FFFFFFF}")`, Context.Empty],
27 | [`'use strict'; ('\\1')`, Context.Empty],
28 | [`'use strict'; ('\\4')`, Context.Empty],
29 | [`'use strict'; ('\\11')`, Context.Empty],
30 | [`'use strict'; ('\\00')`, Context.Empty]
31 | ]);
32 |
--------------------------------------------------------------------------------
/src/scanner/common.ts:
--------------------------------------------------------------------------------
1 | import { Chars, skipSingleLineComment } from './';
2 | import { ParserState } from '../parser/common';
3 | import { report, Errors } from '../errors';
4 |
5 | export function skipMeta(parser: ParserState, source: string): void {
6 | let index = 0;
7 |
8 | if (index === parser.length) return;
9 |
10 | // Absorb any byte order mark at the start
11 |
12 | if (source.charCodeAt(index) === Chars.ByteOrderMark) parser.index = index += 1;
13 |
14 | if (index < source.length && source.charCodeAt(index) === Chars.Hash) {
15 | index++;
16 |
17 | if (index < source.length && source.charCodeAt(index) === Chars.Exclamation) {
18 | parser.index = skipSingleLineComment(parser, source, index);
19 | } else {
20 | report(parser, Errors.UnexpectedToken, '#');
21 | }
22 | }
23 | }
24 |
25 | export function readNext(parser: ParserState): number {
26 | parser.index++;
27 | if (parser.index >= parser.length) report(parser, Errors.UnterminatedString);
28 | return parser.source.charCodeAt(parser.index);
29 | }
30 |
31 | // Converts an ASCII alphanumeric digit [0-9a-zA-Z] to number as if in base-36.
32 |
33 | export function toHex(code: number): number {
34 | code -= Chars.Zero;
35 | if (code <= 9) return code;
36 | code = (code | 0x20) - (Chars.LowerA - Chars.Zero);
37 | if (code <= 5) return code + 10;
38 | return -1;
39 | }
40 |
41 | // Optimized version of 'fromCodePoint'
42 |
43 | export function fromCodePoint(codePoint: number): string {
44 | return codePoint <= 65535
45 | ? String.fromCharCode(codePoint)
46 | : String.fromCharCode(codePoint >>> 10) + String.fromCharCode(codePoint & 0x3ff);
47 | }
48 |
--------------------------------------------------------------------------------
/src/scanner/tokenization.ts:
--------------------------------------------------------------------------------
1 | import { Token, KeywordDescTable } from '../token';
2 | import { ParserState, Context } from '../parser/common';
3 |
4 | // https://tc39.es/ecma262/#sec-ecmascript-language-lexical-grammar
5 |
6 | export function convertTokenType(parser: ParserState, context: Context): any {
7 | let type = 'Punctuator';
8 | let value = parser.tokenValue;
9 | const t = parser.token;
10 | switch (t) {
11 | case Token.NumericLiteral:
12 | type = 'NumericLiteral';
13 | break;
14 | case Token.StringLiteral:
15 | type = 'StringLiteral';
16 | break;
17 | case Token.FalseKeyword:
18 | case Token.TrueKeyword:
19 | type = 'BooleanLiteral';
20 | value = KeywordDescTable[t & Token.Kind] === 'true';
21 | break;
22 | case Token.NullKeyword:
23 | type = 'NullLiteral';
24 | value = null;
25 | break;
26 | case Token.ThisKeyword:
27 | type = 'Keyword';
28 | value = 'this';
29 | break;
30 | case Token.RegularExpression:
31 | type = 'RegularExpression';
32 | value = {
33 | value: parser.tokenValue,
34 | regex: parser.tokenRegExp
35 | };
36 | break;
37 | case Token.TemplateCont:
38 | case Token.TemplateTail:
39 | type = 'Template';
40 | break;
41 | default:
42 | if ((t & Token.IsIdentifier) === Token.IsIdentifier) {
43 | type = 'Identifier';
44 | } else if ((t & Token.Keyword) === Token.Keyword) {
45 | type = 'Keyword';
46 | } else {
47 | value = KeywordDescTable[t & Token.Kind];
48 | }
49 | break;
50 | }
51 |
52 | if (context & Context.OptionsLoc) parser.onToken(type, value, parser.start, parser.index);
53 | else parser.onToken(type, value);
54 | }
55 |
--------------------------------------------------------------------------------
/test/parser/miscellaneous/asi.ts:
--------------------------------------------------------------------------------
1 | import * as t from 'assert';
2 | import { parseScript, parseModule } from '../../../src/seafox';
3 |
4 | describe('Miscellaneous - ASI', () => {
5 | for (const arg of [
6 | `var x=0, y=0;\nvar z=\nx\n++\n++\ny`,
7 | `for(\nfalse\n) {\nbreak;\n}`,
8 | `for(false;false;;false) { break; }`,
9 | `\n while(false)`,
10 | `do {}; \n while(false)`,
11 | '{} * 1',
12 | `for header is (false \n false \n)`,
13 | 'if (false) x = 1 else x = -1',
14 | `try {
15 | throw
16 | 1;
17 | } catch(e) {
18 | }`,
19 | `var x = 0;
20 | x
21 | ++;`,
22 | `var x = 1;
23 | x
24 | --;`,
25 | `for(;
26 | ) {
27 | break;
28 | }`,
29 | `for(
30 | false
31 | ;) {
32 | break;
33 | }`,
34 | `for(
35 | ;
36 | ) {
37 | break;
38 | }`,
39 | `for(
40 | ) {
41 | break;
42 | }`,
43 | `for(false
44 | false
45 | ) {
46 | break;
47 | }`,
48 | `do
49 | while (false)`
50 | ]) {
51 | it(`${arg}`, () => {
52 | t.throws(() => {
53 | parseScript(`${arg}`, {
54 | disableWebCompat: true,
55 | impliedStrict: true
56 | });
57 | });
58 | });
59 |
60 | it(`${arg}`, () => {
61 | t.throws(() => {
62 | parseModule(`${arg}`);
63 | });
64 | });
65 | }
66 |
67 | for (const arg of [
68 | `;;1;;1;;1`,
69 | '"foo"\nx',
70 | `function f(){\n'foo';\n}`,
71 | 'function f(){\n"foo"\n}',
72 | '"ignore me"\n++x',
73 | '("use strict"); foo = 42;',
74 | 'function f(){ 123; "use strict";}'
75 | ]) {
76 | it(`${arg}`, () => {
77 | t.doesNotThrow(() => {
78 | parseScript(`${arg}`);
79 | });
80 | });
81 | it(`${arg}`, () => {
82 | t.doesNotThrow(() => {
83 | parseModule(`${arg}`);
84 | });
85 | });
86 | }
87 | });
88 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | map-1: &filter_only_master
4 | filters:
5 | branches:
6 | only:
7 | - master
8 |
9 | map-2: &filter_only_dev
10 | filters:
11 | branches:
12 | only:
13 | - dev
14 |
15 | map-3: &filter_ignore_dev
16 | filters:
17 | branches:
18 | ignore:
19 | - dev
20 |
21 | test: &test
22 | parameters:
23 | coverage:
24 | type: boolean
25 | default: true
26 | steps:
27 | - checkout
28 | - run: node --version
29 | - run: npm install
30 | - run: npm run test
31 |
32 | executors:
33 |
34 | docker-circleci:
35 | parameters:
36 | node:
37 | type: string
38 | default: "10.12"
39 | working_directory: ~/repo
40 | docker:
41 | - image: "circleci/node:<< parameters.node >>-stretch-browsers"
42 |
43 | jobs:
44 |
45 | build:
46 | executor: docker-circleci
47 | steps:
48 | - checkout
49 | - run: npm ci
50 | - run: npm run build
51 |
52 | lint:
53 | executor: docker-circleci
54 | steps:
55 | - checkout
56 | - run: npm ci
57 | - run: npm run lint
58 |
59 | bundle:
60 | executor: docker-circleci
61 | steps:
62 | - checkout
63 | - run: npm ci
64 | - run: npm run bundle
65 |
66 | test-8:
67 | <<: *test
68 | executor:
69 | name: docker-circleci
70 | node: "8.16.1"
71 |
72 | test-10:
73 | <<: *test
74 | executor:
75 | name: docker-circleci
76 | node: "10.12"
77 |
78 | test-12:
79 | <<: *test
80 | executor:
81 | name: docker-circleci
82 | node: "12.9"
83 |
84 | workflows:
85 | build_test:
86 | jobs:
87 | - build:
88 | <<: *filter_ignore_dev
89 | - lint:
90 | <<: *filter_ignore_dev
91 | - bundle:
92 | <<: *filter_ignore_dev
93 | - test-8:
94 | <<: *filter_ignore_dev
95 | - test-10:
96 | <<: *filter_ignore_dev
97 | - test-12:
98 | <<: *filter_ignore_dev
99 |
--------------------------------------------------------------------------------
/scripts/bump-dev-version.js:
--------------------------------------------------------------------------------
1 | const { readFile, writeFile } = require('fs');
2 | const project = require('./project');
3 |
4 | async function loadPackageJson(isLockfile) {
5 | const file = isLockfile ? 'package-lock.json' : 'package.json';
6 | return new Promise((resolve, reject) => {
7 | readFile(project[file].path, (err, data) => {
8 | if (err) {
9 | reject(err);
10 | }
11 | const str = data.toString('utf8');
12 | const json = JSON.parse(str);
13 | resolve(json);
14 | });
15 | });
16 | }
17 |
18 | async function savePackageJson(pkg, isLockfile) {
19 | const file = isLockfile ? 'package-lock.json' : 'package.json';
20 | return new Promise((resolve, reject) => {
21 | const str = JSON.stringify(pkg, null, 2);
22 | writeFile(project[file].path, str, { encoding: 'utf8' }, err => {
23 | if (err) {
24 | reject(err);
25 | }
26 | resolve();
27 | });
28 | });
29 | }
30 |
31 | async function run() {
32 | /*eslint-disable*/
33 | const versionRegExp = /(\d+)\.(\d+)\.(\d+)($|\-)/;
34 | const match = project.pkg.version.match(versionRegExp);
35 | if (match === null) {
36 | throw new Error(`package.json 'version' should match ${versionRegExp}`);
37 | }
38 | const major = match[1];
39 | const minor = match[2];
40 | const patch = match[3];
41 | console.log(`current version: ${major}.${minor}.${patch}`);
42 |
43 | const raw = new Date()
44 | .toISOString()
45 | .replace(/:|T|\.|-/g, '')
46 | .slice(0, 8);
47 | const y = raw.slice(0, 4);
48 | const m = raw.slice(4, 6);
49 | const d = raw.slice(6, 8);
50 | const datestamp = `${y}${m}${d}`;
51 | const newVersion = `${major}.${minor}.${patch}-dev.${datestamp}`;
52 | console.log(`new version: ${newVersion}`);
53 |
54 | const packageJson = await loadPackageJson(false);
55 | packageJson.version = newVersion;
56 | await savePackageJson(packageJson, false);
57 |
58 | const packageLockJson = await loadPackageJson(true);
59 | packageLockJson.version = newVersion;
60 | await savePackageJson(packageLockJson, true);
61 | }
62 |
63 | run().then(() => {
64 | console.log(`Done.`);
65 | });
66 |
--------------------------------------------------------------------------------
/src/scanner/comments.ts:
--------------------------------------------------------------------------------
1 | import { ParserState, Context, Flags } from '../parser/common';
2 | import { report, Errors } from '../errors';
3 | import { unicodeLookup, Chars } from './';
4 |
5 | export function skipSingleHTMLComment(parser: ParserState, context: Context, source: string, i: number): number {
6 | if ((context & 0b00000000000000000000100000010000) > 0) report(parser, Errors.HtmlCommentInModule);
7 | return skipSingleLineComment(parser, source, i++);
8 | }
9 |
10 | export function skipSingleLineComment(parser: ParserState, source: string, i: number): number {
11 | // The line terminator at the end of the line is not considered
12 | // to be part of the single-line comment; it is recognized
13 | // separately by the lexical grammar and becomes part of the
14 | // stream of input elements for the syntactic grammar
15 | let char = source.charCodeAt(i);
16 | while (i < parser.length && ((unicodeLookup[(char >>> 5) + 69632] >>> char) & 31 & 1) === 0) {
17 | char = source.charCodeAt(++i);
18 | }
19 |
20 | return i;
21 | }
22 |
23 | export function skipMultiLineComment(parser: ParserState, source: string, length: number, i: number): number | void {
24 | let lastIsCR: 0 | 1 = 0;
25 | let isNewLine: 0 | 1 = 0;
26 | let char = source.charCodeAt(i++);
27 |
28 | while (i < length) {
29 | if (char < 0b00000000000000000000000000101011) {
30 | if (char === Chars.Asterisk) {
31 | while (char === Chars.Asterisk) {
32 | char = source.charCodeAt(i++);
33 | }
34 | if (char === Chars.Slash) {
35 | // Fixes a edge case whith directive parsing when a multiline commented is
36 | // on two or more lines.
37 | if (isNewLine === 0) parser.flags |= Flags.CommentStart;
38 | return i;
39 | }
40 | }
41 |
42 | if (char === Chars.CarriageReturn) {
43 | parser.curLine++;
44 | parser.newLine = lastIsCR = 1;
45 | parser.offset = i;
46 | isNewLine = 1;
47 | }
48 |
49 | if (char === Chars.LineFeed) {
50 | if (lastIsCR === 0) parser.curLine++;
51 | lastIsCR = 0;
52 | parser.newLine = 1;
53 | parser.offset = i;
54 | isNewLine = 1;
55 | }
56 | }
57 | if ((char & ~0b00000000000000000000000000000001) === Chars.LineSeparator) {
58 | parser.offset = i;
59 | parser.newLine = 1;
60 | parser.curLine++;
61 | lastIsCR = 0;
62 | isNewLine = 0;
63 | }
64 | char = source.charCodeAt(i++);
65 | }
66 |
67 | report(parser, Errors.UnterminatedComment);
68 | }
69 |
--------------------------------------------------------------------------------
/test/scanner/regexp.ts:
--------------------------------------------------------------------------------
1 | import * as t from 'assert';
2 | import { Context } from '../../src/parser/common';
3 | import { create } from '../../src/parser/core';
4 | import { Token } from '../../src/token';
5 | import { scan } from '../../src/scanner/scan';
6 |
7 | describe('Scanner - Regular expression', () => {
8 | const tokens: Array<[Context, Token, string, any]> = [
9 | [Context.OptionsRaw, Token.RegularExpression, '/a/', /a/],
10 | [Context.OptionsRaw, Token.RegularExpression, '/(\\w+)\\s(\\w+)/g', /(\w+)\s(\w+)/g],
11 | [Context.OptionsRaw, Token.RegularExpression, '/[a/]/', /[a/]/],
12 | [Context.OptionsRaw, Token.RegularExpression, '/[\\ufdd0-\\ufdef]/', /[\ufdd0-\ufdef]/],
13 | [Context.OptionsRaw, Token.RegularExpression, '/[\\u{FDD0}-\\u{FDEF}]/u', /[\u{FDD0}-\u{FDEF}]/u]
14 | ];
15 |
16 | for (const [ctx, token, op, value] of tokens) {
17 | it(`scans '${op}' at the end`, () => {
18 | const parser = create(op);
19 | const found = scan(parser, ctx, op, 1, op.length, Token.EOF, 0, true, 1);
20 |
21 | t.deepEqual(
22 | {
23 | token: found,
24 | hasNext: parser.index < parser.length,
25 | line: parser.curLine,
26 | value: parser.tokenValue,
27 | column: parser.index - parser.offset
28 | },
29 | {
30 | token: token,
31 | hasNext: false,
32 | value: value,
33 | line: 1,
34 | column: op.length
35 | }
36 | );
37 | });
38 |
39 | it(`scans '${op}' with more to go`, () => {
40 | const parser = create(`${op} rest`);
41 | const found = scan(parser, ctx, op, 1, op.length, Token.EOF, 0, true, 1);
42 |
43 | t.deepEqual(
44 | {
45 | token: found,
46 | hasNext: parser.index < parser.source.length,
47 | line: parser.curLine,
48 | column: parser.index - parser.offset
49 | },
50 | {
51 | token,
52 | hasNext: true,
53 | line: 1,
54 | column: op.length
55 | }
56 | );
57 | });
58 | }
59 |
60 | function fail(name: string, source: string, context: Context) {
61 | it(name, () => {
62 | const parser = create(source);
63 | t.throws(() => scan(parser, context, source, 1, source.length, Token.EOF, 0, true, 1));
64 | });
65 | }
66 |
67 | fail('fails on /a', '/a', Context.Empty);
68 | fail('fails on /', '/', Context.Empty);
69 | fail('fails on /\u2028', '/\u2028', Context.Empty);
70 | fail('fails on /\u2029', '/\u2029', Context.Empty);
71 | });
72 |
--------------------------------------------------------------------------------
/scripts/prettier.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 | 'use strict';
8 |
9 | // Based on similar script in Jest
10 | // https://github.com/facebook/jest/blob/a7acc5ae519613647ff2c253dd21933d6f94b47f/scripts/prettier.js
11 |
12 | /*eslint no-process-exit:0*/
13 | const chalk = require('chalk');
14 | const glob = require('glob');
15 | const prettier = require('prettier');
16 | const fs = require('fs');
17 | const listChangedFiles = require('./list-changed-files');
18 | const prettierConfigPath = require.resolve('../prettier.config.js');
19 | const prettierIgnorePath = require.resolve('../.prettierignore');
20 |
21 | const mode = process.argv[2] || 'check';
22 | const shouldWrite = mode === 'write' || mode === 'write-changed';
23 | const onlyChanged = mode === 'check-changed' || mode === 'write-changed';
24 |
25 | const changedFiles = onlyChanged ? listChangedFiles() : null;
26 | let didWarn = false;
27 | let didError = false;
28 |
29 | const files = glob
30 | .sync('**/*.{ts,js}', { ignore: '**/node_modules/**' })
31 | .filter(f => !onlyChanged || changedFiles.has(f));
32 |
33 | if (!files.length) {
34 | return;
35 | }
36 |
37 | files.forEach(file => {
38 | const options = prettier.resolveConfig.sync(file, {
39 | config: prettierConfigPath
40 | });
41 | try {
42 | const fileInfo = prettier.getFileInfo.sync(file, { ignorePath: prettierIgnorePath });
43 | if (fileInfo.ignored) {
44 | return;
45 | }
46 |
47 | const input = fs.readFileSync(file, 'utf8');
48 | const withParserOptions = {
49 | ...options,
50 | parser: fileInfo.inferredParser
51 | };
52 |
53 | if (shouldWrite) {
54 | const output = prettier.format(input, withParserOptions);
55 | if (output !== input) {
56 | fs.writeFileSync(file, output, 'utf8');
57 | }
58 | } else {
59 | if (!prettier.check(input, withParserOptions)) {
60 | if (!didWarn) {
61 | console.log(
62 | '\n' +
63 | chalk.red(` This project uses prettier to format all ts/js code.\n`) +
64 | chalk.dim(` Please run `) +
65 | chalk.reset('npm run prettier') +
66 | chalk.dim(` and add changes to files listed below to your commit:`) +
67 | `\n\n`
68 | );
69 | didWarn = true;
70 | }
71 | console.log(file);
72 | }
73 | }
74 | } catch (error) {
75 | didError = true;
76 | console.log('\n\n' + error.message);
77 | console.log(file);
78 | }
79 | });
80 |
81 | if (didWarn || didError) {
82 | process.exit(1);
83 | }
84 |
--------------------------------------------------------------------------------
/test/parser/statements/empty.ts:
--------------------------------------------------------------------------------
1 | import { pass } from '../core';
2 | import { Context } from '../../../src/parser/common';
3 |
4 | pass('Statements - Empty (pass)', [
5 | [
6 | `;;;;;;`,
7 | Context.OptionsNext | Context.OptionsLoc,
8 | {
9 | type: 'Program',
10 | sourceType: 'script',
11 | body: [
12 | {
13 | type: 'EmptyStatement',
14 | start: 0,
15 | end: 1,
16 | loc: {
17 | start: {
18 | line: 1,
19 | column: 0
20 | },
21 | end: {
22 | line: 1,
23 | column: 1
24 | }
25 | }
26 | },
27 | {
28 | type: 'EmptyStatement',
29 | start: 1,
30 | end: 2,
31 | loc: {
32 | start: {
33 | line: 1,
34 | column: 1
35 | },
36 | end: {
37 | line: 1,
38 | column: 2
39 | }
40 | }
41 | },
42 | {
43 | type: 'EmptyStatement',
44 | start: 2,
45 | end: 3,
46 | loc: {
47 | start: {
48 | line: 1,
49 | column: 2
50 | },
51 | end: {
52 | line: 1,
53 | column: 3
54 | }
55 | }
56 | },
57 | {
58 | type: 'EmptyStatement',
59 | start: 3,
60 | end: 4,
61 | loc: {
62 | start: {
63 | line: 1,
64 | column: 3
65 | },
66 | end: {
67 | line: 1,
68 | column: 4
69 | }
70 | }
71 | },
72 | {
73 | type: 'EmptyStatement',
74 | start: 4,
75 | end: 5,
76 | loc: {
77 | start: {
78 | line: 1,
79 | column: 4
80 | },
81 | end: {
82 | line: 1,
83 | column: 5
84 | }
85 | }
86 | },
87 | {
88 | type: 'EmptyStatement',
89 | start: 5,
90 | end: 6,
91 | loc: {
92 | start: {
93 | line: 1,
94 | column: 5
95 | },
96 | end: {
97 | line: 1,
98 | column: 6
99 | }
100 | }
101 | }
102 | ],
103 | start: 0,
104 | end: 6,
105 | loc: {
106 | start: {
107 | line: 1,
108 | column: 0
109 | },
110 | end: {
111 | line: 1,
112 | column: 6
113 | }
114 | }
115 | }
116 | ]
117 | ]);
118 |
--------------------------------------------------------------------------------
/test/scanner/whitespace.ts:
--------------------------------------------------------------------------------
1 | import * as t from 'assert';
2 | import { Context } from '../../src/parser/common';
3 | import { scan } from '../../src/scanner/scan';
4 | import { create } from '../../src/parser/core';
5 |
6 | describe('Scanner - Whitespace', () => {
7 | function pass(name: string, opts: any) {
8 | it(name, () => {
9 | const parser = create(opts.source);
10 | scan(parser, Context.Empty, opts.source, 1, opts.source.length, 0, 0, true, /* allowRegExp */ 0);
11 | t.deepEqual(
12 | {
13 | hasNext: parser.index < parser.source.length,
14 | line: parser.curLine,
15 | column: parser.index - parser.offset
16 | },
17 | {
18 | hasNext: opts.hasNext,
19 | line: opts.line,
20 | column: opts.column
21 | }
22 | );
23 | });
24 | }
25 | pass('skips nothing', {
26 | source: '',
27 | hasNext: false,
28 | line: 1,
29 | column: 0
30 | });
31 |
32 | pass('skips tabs', {
33 | source: '\t\t\t\t\t\t\t\t',
34 | hasNext: false,
35 | line: 1,
36 | column: 8
37 | });
38 |
39 | pass('skips vertical tabs', {
40 | source: '\v\v\v\v\v\v\v\v',
41 | hasNext: false,
42 | line: 1,
43 | column: 8
44 | });
45 |
46 | pass('skips spaces', {
47 | source: ' ',
48 | hasNext: false,
49 | line: 1,
50 | column: 8
51 | });
52 |
53 | pass('skips nl + lf', {
54 | source: '\n\r',
55 | hasNext: false,
56 | line: 3,
57 | column: 0
58 | });
59 |
60 | pass('skips lf + nl', {
61 | source: '\r\n ',
62 | hasNext: false,
63 | line: 2,
64 | column: 1
65 | });
66 |
67 | pass('skips spaces', {
68 | source: ' ',
69 | hasNext: false,
70 | line: 1,
71 | column: 8
72 | });
73 |
74 | pass('skips exotic whitespace (1)', {
75 | source:
76 | '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF',
77 | hasNext: false,
78 | line: 5,
79 | column: 1
80 | });
81 |
82 | pass('skips exotic whitespace (1)', {
83 | source: '\u2003',
84 | hasNext: false,
85 | line: 1,
86 | column: 1
87 | });
88 |
89 | pass('skips exotic whitespace (2)', {
90 | source: '\u8202',
91 | hasNext: false,
92 | line: 1,
93 | column: 1
94 | });
95 |
96 | pass('skips exotic whitespace (3)', {
97 | source: '\u8197\u8202',
98 | hasNext: false,
99 | line: 1,
100 | column: 2
101 | });
102 |
103 | pass('skips exotic whitespace (4)', {
104 | source: '\u2001\u2002\u2003',
105 | hasNext: false,
106 | line: 1,
107 | column: 3
108 | });
109 |
110 | pass('skips mixed whitespace', {
111 | source: ' \t \r\n \n\r \v\f\t ',
112 | hasNext: false,
113 | line: 4,
114 | column: 5
115 | });
116 | });
117 |
--------------------------------------------------------------------------------
/test/parser/expressions/object-spread.ts:
--------------------------------------------------------------------------------
1 | import { Context } from '../../../src/parser/common';
2 | import { parseRoot } from '../../../src/seafox';
3 | import * as t from 'assert';
4 |
5 | describe('Expressions - Object spread', () => {
6 | for (const arg of [
7 | '{ ...var z = y}',
8 | '{ ...var}',
9 | '{ ...foo bar}',
10 | '{* ...foo}',
11 | '{get ...foo}',
12 | '{set ...foo}',
13 | '{async ...foo}',
14 | 'return ...[1,2,3];',
15 | 'var ...x = [1,2,3];',
16 | 'var [...x,] = [1,2,3];',
17 | 'var [...x, y] = [1,2,3];',
18 | 'var { x } = {x: ...[1,2,3]}'
19 | ]) {
20 | it(`x = ${arg}`, () => {
21 | t.throws(() => {
22 | parseRoot(`x = ${arg};`, Context.Empty);
23 | });
24 | });
25 |
26 | it(`function fn() { 'use strict';${arg}} fn();`, () => {
27 | t.throws(() => {
28 | parseRoot(`function fn() { 'use strict';${arg}} fn();`, Context.Empty);
29 | });
30 | });
31 |
32 | it(`function fn() { ${arg}} fn();`, () => {
33 | t.throws(() => {
34 | parseRoot(`function fn() { ${arg}} fn();`, Context.Empty);
35 | });
36 | });
37 |
38 | it(`"use strict"; x = ${arg}`, () => {
39 | t.throws(() => {
40 | parseRoot(`x = ${arg};`, Context.Empty);
41 | });
42 | });
43 | }
44 |
45 | // Spread Array
46 |
47 | for (const arg of ['[...]', '[a, ...]', '[..., ]', '[..., ...]', '[ (...a)]']) {
48 | it(`${arg}`, () => {
49 | t.throws(() => {
50 | parseRoot(`${arg};`, Context.OptionsDisableWebCompat);
51 | });
52 | });
53 |
54 | it(`"use strict"; ${arg}`, () => {
55 | t.throws(() => {
56 | parseRoot(`"use strict"; ${arg};`, Context.Empty);
57 | });
58 | });
59 | }
60 |
61 | for (const arg of [
62 | '[...a]',
63 | '[a, ...b]',
64 | '[...a,]',
65 | '[...a, ,]',
66 | '[, ...a]',
67 | '[...a, ...b]',
68 | '[...a, , ...b]',
69 | '[...[...a]]',
70 | '[, ...a]',
71 | '[, , ...a]',
72 | '{ ...y }',
73 | '{ a: 1, ...y }',
74 | '{ b: 1, ...y }',
75 | '{ y, ...y}',
76 | '{ ...z = y}',
77 | '{ ...y, y }',
78 | '{ ...y, ...y}',
79 | '{ a: 1, ...y, b: 1}',
80 | '{ ...y, b: 1}',
81 | '{ ...1}',
82 | '{ ...null}',
83 | '{ ...undefined}',
84 | '{ ...1 in {}}',
85 | '{ ...[]}',
86 | '{ ...async function() { }}',
87 | '{ ...async () => { }}',
88 | '{ ...new Foo()}'
89 | ]) {
90 | it(`x = ${arg}`, () => {
91 | t.doesNotThrow(() => {
92 | parseRoot(`x = ${arg};`, Context.Empty);
93 | });
94 | });
95 |
96 | it(`x = ${arg}`, () => {
97 | t.doesNotThrow(() => {
98 | parseRoot(`x = ${arg};`, Context.OptionsNext | Context.Module);
99 | });
100 | });
101 |
102 | it(`"use strict"; x = ${arg}`, () => {
103 | t.doesNotThrow(() => {
104 | parseRoot(`x = ${arg};`, Context.Empty);
105 | });
106 | });
107 | }
108 | });
109 |
--------------------------------------------------------------------------------
/test/parser/miscellaneous/lexical.ts:
--------------------------------------------------------------------------------
1 | import { parseRoot } from '../../../src/seafox';
2 | import * as t from 'assert';
3 | import { Context } from '../../../src/parser/common';
4 |
5 | describe('Miscellaneous - Lexical', () => {
6 | for (const arg of [
7 | '{ async function f() {} var f; }',
8 | 'let x; { var x; }',
9 | 'let x; var x;',
10 | 'for (const x in y) { var x; }',
11 | 'for (let x in y) { var x; }',
12 | 'for (const x = y;;) { var x; }',
13 | 'for (let x;;) { var x; }',
14 | 'for (const x of y) { var x; }',
15 | 'for (let x of y) { var x; }',
16 | 'for (let a, b, x, d;;) { var foo; var bar; { var doo, x, ee; } }',
17 | 'function g(){ const f = 1; function f() {} }',
18 | 'function g(){ let f = 1; function f() {} }',
19 | 'function g(){ { function f() {} var f = 1; } }',
20 | 'const f = 1; function f() {}',
21 | 'let f = 1; function f() {}',
22 | '{ function f() {} var f = 1; }',
23 | 'const x = a; function x(){};',
24 | 'let x = a; function x(){};',
25 | 'class o {f(){ const x = y; function x(){} }}',
26 | 'class o {f(){ const x = y; var x; }}',
27 | 'class o {f(){ let x; function x(){} }}',
28 | 'class o {f(){ let x; var x; }}',
29 | 'o = {f(){ const x = y; function x(){} }}',
30 | 'o = {f(){ const x = y; var x; }}',
31 | 'o = {f(){ let x; function x(){} }}',
32 | 'o = {f(){ let x; var x; }}',
33 | 'switch (x) { case a: const foo = x; break; case b: var foo = x; break; }',
34 | 'switch (x) { case a: let foo; break; case b: var foo; break; }',
35 | 'let x; for (;;) { var x; }',
36 | 'let x; { var x }',
37 | 'for (const x in obj) { var x = 13 }',
38 | 'for (const x of obj) { var x = 14 }',
39 | 'for (const x = 1;;) { var x = 2 }',
40 | 'try { async function *f(){} var f } catch (e) {}',
41 | 'try { async function f(){} var f } catch (e) {}',
42 | 'try { function *f(){} var f } catch (e) {}',
43 | 'try { function f(){} var f } catch (e) {}',
44 | '{ async function *f(){} var f }',
45 | `{ async function f(){} var f }`,
46 | `{ function *f(){} var f }`,
47 | `{ function f(){} var f }`,
48 | `switch (0) { case 1: function f() {} default: var f }`,
49 | `for (let x in {}) { var x; }`
50 | ]) {
51 | it(`${arg}`, () => {
52 | t.throws(() => {
53 | parseRoot(`${arg}`, Context.Empty);
54 | });
55 | });
56 |
57 | it(`${arg}`, () => {
58 | t.throws(() => {
59 | parseRoot(`${arg}`, Context.Empty);
60 | });
61 | });
62 | }
63 |
64 | for (const arg of [
65 | `try {} catch (e) { var e = x; }`,
66 | `try {} catch (e) { for (var e = 1;;) {} }`,
67 | `try {} catch (e) { for (var e in y) {} }`,
68 | `try {} catch (e) { for (var e of y) {} }`,
69 | `try {} catch (e) { { var e = x; } }`
70 | ]) {
71 | it(`${arg}`, () => {
72 | t.throws(() => {
73 | parseRoot(`${arg}`, Context.OptionsDisableWebCompat);
74 | });
75 | });
76 |
77 | it(`${arg}`, () => {
78 | t.doesNotThrow(() => {
79 | parseRoot(`${arg}`, Context.Empty);
80 | });
81 | });
82 | }
83 | });
84 |
--------------------------------------------------------------------------------
/scripts/bundle.js:
--------------------------------------------------------------------------------
1 | const { join } = require('path');
2 | const rollup = require('rollup');
3 | const typescript2 = require('rollup-plugin-typescript2');
4 | const { terser } = require('rollup-plugin-terser');
5 | const ts = require('typescript');
6 | const project = require('./project');
7 |
8 | bundle();
9 |
10 | async function bundle() {
11 | if (process.argv.slice(2)[0] === 'bench') {
12 | await bunldeCJS();
13 | } else {
14 | await bundleES6();
15 | await bundleES5();
16 | }
17 | }
18 |
19 | // bundle cjs(es6)
20 | async function bunldeCJS() {
21 | console.log(`creating cjs bundle`);
22 |
23 | const bundle = await rollup.rollup({
24 | input: project.entry.path,
25 | plugins: [
26 | typescript2({
27 | tsconfig: project['tsconfig.json'].path,
28 | typescript: ts
29 | })
30 | ]
31 | });
32 |
33 | const file = join(project.dist.path, `seafox.cjs.js`);
34 |
35 | console.log(`writing ${file}`);
36 |
37 | await bundle.write({
38 | file,
39 | name: 'seafox',
40 | format: 'cjs'
41 | });
42 | console.log(`done`);
43 | }
44 |
45 | // bundle es6()
46 | async function bundleES6() {
47 | for (const type of ['normal', 'minified']) {
48 | console.log(`creating ${type} bundle`);
49 |
50 | const bundle = await rollup.rollup({
51 | input: project.entry.path,
52 | plugins: [
53 | typescript2({
54 | tsconfig: project['tsconfig.json'].path,
55 | typescript: ts
56 | }),
57 | ...(type === 'minified' ? [terser()] : [])
58 | ]
59 | });
60 |
61 | const suffix = type === 'minified' ? '.min' : '';
62 |
63 | let minfile = join(project.dist.path, `seafox.esm${suffix}.js`);
64 |
65 | console.log(`writing ${minfile}`);
66 |
67 | await bundle.write({
68 | file: minfile,
69 | name: 'seafox',
70 | format: 'esm'
71 | });
72 |
73 | minfile = join(project.dist.path, `seafox.umd${suffix}.js`);
74 |
75 | console.log(`writing ${minfile}`);
76 |
77 | await bundle.write({
78 | file: minfile,
79 | exports: 'named',
80 | name: 'seafox',
81 | format: 'umd'
82 | });
83 | }
84 | }
85 |
86 | // bundle es5(umd)
87 | async function bundleES5() {
88 | for (const type of ['normal', 'minified']) {
89 | console.log(`creating ${type} es5 bundle`);
90 |
91 | const bundle = await rollup.rollup({
92 | input: project.entry.path,
93 | plugins: [
94 | typescript2({
95 | tsconfig: project['tsconfig.json'].path,
96 | tsconfigOverride: { compilerOptions: { target: 'es5' } },
97 | typescript: ts
98 | }),
99 | ...(type === 'minified' ? [terser()] : [])
100 | ]
101 | });
102 |
103 | const suffix = type === 'minified' ? '.min' : '';
104 |
105 | const fleName = join(project.dist.path, `seafox.umd.es5${suffix}.js`);
106 |
107 | console.log(`writing ${fleName}`);
108 |
109 | await bundle.write({
110 | file: fleName,
111 | exports: 'named',
112 | name: 'seafox',
113 | format: 'umd'
114 | });
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/test/test262-parser-tests/parser-tests.ts:
--------------------------------------------------------------------------------
1 | import { parseScript, parseModule } from '../../src/seafox';
2 | import { readdirSync, readFileSync } from 'fs';
3 | import * as t from 'assert';
4 |
5 | // Note! Some tests are skipped because they are either invalid
6 | // or should fail only in strict mode or module goal and I don't
7 | // don't test for that.
8 | //
9 | // AnnexB is on by default.
10 |
11 | const Test262Dir = 'node_modules/test262-parser-tests';
12 |
13 | const expectations = {
14 | pass: ['1a1c717109ab67e1.js', '206ebb4e67a6daa9.js', '4ad6e3a59e27e9b1.js', 'fc020c065098cbd5.js'],
15 | explicit: ['1a1c717109ab67e1.js', '206ebb4e67a6daa9.js', 'fc020c065098cbd5.js', '4ad6e3a59e27e9b1.js'],
16 | fail: [
17 | 'e4a43066905a597b.js',
18 | 'bf49ec8d96884562.js',
19 | '8af69d8f15295ed2.js',
20 | '84633c379e4010bf.js',
21 | '78c215fabdf13bae.js',
22 | 'f4467d719dcee086.js',
23 | '66e383bfd18e66ab.js',
24 | '647e21f8f157c338.js',
25 | '7b876ca5139f1ca8.js',
26 | 'e3fbcf63d7e43ead.js',
27 | 'fd2a45941e114896.js',
28 | '89036b2edb64c00c.js',
29 | 'abe5f49acb8e132a.js',
30 | 'de15bc95fed5eebf.module.js'
31 | ],
32 | early: [
33 | 'ec31fa5e521c5df4.js',
34 | 'e262ea7682c36f92.js',
35 | 'be7329119eaa3d47.js',
36 | '4de83a7417cd30dd.js',
37 | '1aff49273f3e3a98.js',
38 | '12a74c60f52a60de.js',
39 | '0f5f47108da5c34e.js',
40 | '2fcc5b7e8d0ff3c9.js',
41 | '4435f19f2a2a24bd.js'
42 | ]
43 | };
44 |
45 | const parse = (src: string, module: boolean) => (module ? parseModule : parseScript)(src);
46 |
47 | const isModule = (val: string) => /\.module\.js/.test(val);
48 |
49 | describe('Test262 Parser tests', () => {
50 | describe('Pass', () => {
51 | for (const f of readdirSync(`${Test262Dir}/pass`)) {
52 | if (expectations.pass.indexOf(f) !== -1) continue;
53 | it(`Should pass - [${f}]`, () => {
54 | t.doesNotThrow(() => {
55 | parse(readFileSync(`${Test262Dir}/pass/${f}`, 'utf8'), isModule(f));
56 | });
57 | });
58 | }
59 | });
60 |
61 | describe('Pass explicit', () => {
62 | for (const f of readdirSync(`${Test262Dir}/pass-explicit`)) {
63 | if (expectations.explicit.indexOf(f) !== -1) continue;
64 | it(`Should pass - [${f}]`, () => {
65 | t.doesNotThrow(() => {
66 | parse(readFileSync(`${Test262Dir}/pass-explicit/${f}`, 'utf8'), isModule(f));
67 | });
68 | });
69 | }
70 | });
71 |
72 | describe('Fail', () => {
73 | for (const f of readdirSync(`${Test262Dir}/fail`)) {
74 | if (expectations.fail.indexOf(f) !== -1) continue;
75 | it(`Should fail on - [${f}]`, () => {
76 | t.throws(() => {
77 | parse(readFileSync(`${Test262Dir}/fail/${f}`, 'utf8'), isModule(f));
78 | });
79 | });
80 | }
81 | });
82 |
83 | describe('Early errors', () => {
84 | for (const f of readdirSync(`${Test262Dir}/early`)) {
85 | if (expectations.early.indexOf(f) !== -1) continue;
86 | it(`should fail on early error [${f}]`, () => {
87 | t.throws(() => {
88 | parse(readFileSync(`${Test262Dir}/early/${f}`, 'utf8'), isModule(f));
89 | });
90 | });
91 | }
92 | });
93 | });
94 |
--------------------------------------------------------------------------------
/src/scanner/template.ts:
--------------------------------------------------------------------------------
1 | import { Token } from '../token';
2 | import { ParserState, Context } from '../parser/common';
3 | import { Chars, fromCodePoint, readNext, scanEscapeSequence, Escape, handleStringError } from './';
4 |
5 | export function scanTemplate(parser: ParserState, context: Context, source: string): Token {
6 | const { index: start } = parser;
7 | let ret: string | null = '';
8 | let token: Token = Token.TemplateTail;
9 | let char = readNext(parser);
10 |
11 | while (char !== Chars.Backtick) {
12 | if (char === Chars.Dollar) {
13 | if (source.charCodeAt(parser.index + 1) === Chars.LeftBrace) {
14 | parser.index++;
15 | token = Token.TemplateCont;
16 | break;
17 | }
18 | ret += '$';
19 | } else if (char < 0x5d) {
20 | if (char === Chars.Backslash) {
21 | char = readNext(parser);
22 |
23 | if (char >= 0x7d) {
24 | ret += fromCodePoint(char);
25 | } else {
26 | const code = scanEscapeSequence(parser, context | Context.Strict, source, char);
27 |
28 | if (code >= 0) {
29 | ret += fromCodePoint(code);
30 | parser.index--;
31 | } else if (code !== Escape.Empty && context & Context.TaggedTemplate) {
32 | ret = null;
33 | char = scanBadTemplate(parser, source);
34 | if (char < 0) {
35 | token = Token.TemplateCont;
36 | }
37 | break;
38 | } else {
39 | handleStringError(parser, code as Escape, /* isTemplate */ 1);
40 | }
41 | }
42 | } else {
43 | if (char === Chars.CarriageReturn) {
44 | if (parser.index < parser.length && source.charCodeAt(parser.index + 1) === Chars.LineFeed) {
45 | ret += fromCodePoint(char);
46 | char = source.charCodeAt(parser.index);
47 | parser.curLine++;
48 | }
49 | parser.offset = parser.start = parser.index;
50 | parser.curLine++;
51 | } else if (char === Chars.LineFeed) {
52 | parser.offset = parser.start = parser.index;
53 | parser.curLine++;
54 | }
55 |
56 | ret += fromCodePoint(char);
57 | }
58 | } else {
59 | if ((char ^ Chars.LineSeparator) <= 1) {
60 | parser.offset = parser.start = parser.index;
61 | parser.curLine++;
62 | }
63 | ret += fromCodePoint(char);
64 | }
65 |
66 | char = readNext(parser);
67 | }
68 |
69 | parser.index++;
70 | parser.tokenValue = ret;
71 | parser.tokenRaw = source.slice(start + 1, parser.index - (token === Token.TemplateTail ? 1 : 2));
72 |
73 | return token;
74 | }
75 |
76 | function scanBadTemplate(parser: ParserState, source: string): number {
77 | let char = source.charCodeAt(parser.index);
78 |
79 | while (char !== Chars.Backtick) {
80 | if (char === Chars.Dollar) {
81 | const index = parser.index + 1;
82 | if (index < source.length && source.charCodeAt(index) === Chars.LeftBrace) {
83 | parser.index = index;
84 | return -char;
85 | }
86 | }
87 |
88 | char = readNext(parser);
89 | }
90 |
91 | return char;
92 | }
93 |
94 | export function scanTemplateTail(parser: ParserState, context: Context): Token {
95 | parser.index--;
96 | return scanTemplate(parser, context, parser.source);
97 | }
98 |
--------------------------------------------------------------------------------
/src/scanner/chars.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A list of character constants with much more human-readable names.
3 | */
4 | export const enum Chars {
5 | LastUnicodeChar = 0x10ffff,
6 |
7 | Null = 0x00,
8 |
9 | Backspace = 0x08,
10 | Tab = 0x09,
11 | LineFeed = 0x0a,
12 | VerticalTab = 0x0b,
13 | FormFeed = 0x0c,
14 | CarriageReturn = 0x0d,
15 |
16 | Space = 0x20,
17 | Exclamation = 0x21,
18 | DoubleQuote = 0x22,
19 | Hash = 0x23,
20 | Dollar = 0x24,
21 | Percent = 0x25,
22 | Ampersand = 0x26,
23 | SingleQuote = 0x27,
24 | LeftParen = 0x28,
25 | RightParen = 0x29,
26 | Asterisk = 0x2a,
27 | Plus = 0x2b,
28 | Comma = 0x2c,
29 | Hyphen = 0x2d,
30 | Period = 0x2e,
31 | Slash = 0x2f,
32 |
33 | Zero = 0x30,
34 | One = 0x31,
35 | Two = 0x32,
36 | Three = 0x33,
37 | Four = 0x34,
38 | Five = 0x35,
39 | Six = 0x36,
40 | Seven = 0x37,
41 | Eight = 0x38,
42 | Nine = 0x39,
43 | Colon = 0x3a,
44 | Semicolon = 0x3b,
45 | LessThan = 0x3c,
46 | EqualSign = 0x3d,
47 | GreaterThan = 0x3e,
48 | QuestionMark = 0x3f,
49 |
50 | UpperA = 0x41,
51 | UpperB = 0x42,
52 | UpperC = 0x43,
53 | UpperD = 0x44,
54 | UpperE = 0x45,
55 | UpperF = 0x46,
56 | UpperG = 0x47,
57 | UpperH = 0x48,
58 | UpperI = 0x49,
59 | UpperJ = 0x4a,
60 | UpperK = 0x4b,
61 | UpperL = 0x4c,
62 | UpperM = 0x4d,
63 | UpperN = 0x4e,
64 | UpperO = 0x4f,
65 |
66 | UpperP = 0x50,
67 | UpperQ = 0x51,
68 | UpperR = 0x52,
69 | UpperS = 0x53,
70 | UpperT = 0x54,
71 | UpperU = 0x55,
72 | UpperV = 0x56,
73 | UpperW = 0x57,
74 | UpperX = 0x58,
75 | UpperY = 0x59,
76 | UpperZ = 0x5a,
77 | LeftBracket = 0x5b,
78 | Backslash = 0x5c,
79 | RightBracket = 0x5d,
80 | Caret = 0x5e,
81 | Underscore = 0x5f,
82 |
83 | Backtick = 0x60,
84 | LowerA = 0x61,
85 | LowerB = 0x62,
86 | LowerC = 0x63,
87 | LowerD = 0x64,
88 | LowerE = 0x65,
89 | LowerF = 0x66,
90 | LowerG = 0x67,
91 | LowerH = 0x68,
92 | LowerI = 0x69,
93 | LowerJ = 0x6a,
94 | LowerK = 0x6b,
95 | LowerL = 0x6c,
96 | LowerM = 0x6d,
97 | LowerN = 0x6e,
98 | LowerO = 0x6f,
99 |
100 | LowerP = 0x70,
101 | LowerQ = 0x71,
102 | LowerR = 0x72,
103 | LowerS = 0x73,
104 | LowerT = 0x74,
105 | LowerU = 0x75,
106 | LowerV = 0x76,
107 | LowerW = 0x77,
108 | LowerX = 0x78,
109 | LowerY = 0x79,
110 | LowerZ = 0x7a,
111 | LeftBrace = 0x7b,
112 | VerticalBar = 0x7c,
113 | RightBrace = 0x7d,
114 | Tilde = 0x7e,
115 | Delete = 0x7f,
116 |
117 | NextLine = 0x85,
118 | NonBreakingSpace = 0xa0,
119 |
120 | Ogham = 0x1680,
121 |
122 | EnQuad = 0x2000,
123 | EmQuad = 0x2001,
124 | EnSpace = 0x2002,
125 | EmSpace = 0x2003,
126 | ThreePerEmSpace = 0x2004,
127 | FourPerEmSpace = 0x2005,
128 | SixPerEmSpace = 0x2006,
129 | FigureSpace = 0x2007,
130 | PunctuationSpace = 0x2008,
131 | ThinSpace = 0x2009,
132 | HairSpace = 0x200a,
133 | ZeroWidthSpace = 0x200b,
134 | ZeroWidthJoiner = 0x200c,
135 | ZeroWidthNonJoiner = 0x200d,
136 |
137 | LineSeparator = 0x2028,
138 | ParagraphSeparator = 0x2029,
139 |
140 | NarrowNoBreakSpace = 0x202f,
141 | MathematicalSpace = 0x205f,
142 | IdeographicSpace = 0x3000,
143 |
144 | ZeroWidthNoBreakSpace = 0xfeff,
145 |
146 | ByteOrderMark = 0xffef
147 | }
148 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "seafox",
3 | "version": "1.7.1",
4 | "description": "A blazing fast 100% spec compliant, self-hosted javascript parser written in Typescript",
5 | "main": "dist/seafox.umd.min.js",
6 | "module": "dist/seafox.esm.min.js",
7 | "jsnext:main": "dist/seafox.esm.min.js",
8 | "browser": "dist/seafox.umd.min.js",
9 | "types": "dist/seafox.d.ts",
10 | "typings": "dist/seafox.d.ts",
11 | "author": "KFlash",
12 | "license": "ISC",
13 | "homepage": "https://github.com/kflash/seafox",
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/kflash/seafox"
17 | },
18 | "bugs": {
19 | "url": "https://github.com/kflash/seafox/issues"
20 | },
21 | "keywords": [
22 | "parsing",
23 | "ecmascript",
24 | "javascript",
25 | "parser",
26 | "estree",
27 | "es2015",
28 | "es2016",
29 | "es2017",
30 | "es2018",
31 | "es2019",
32 | "es2020",
33 | "esnext",
34 | "performance",
35 | "acorn",
36 | "lexer",
37 | "ast",
38 | "lightweight"
39 | ],
40 | "files": [
41 | "dist/*.min.js",
42 | "dist/**/*.d.ts",
43 | "README.md",
44 | "LICENSE.md"
45 | ],
46 | "publishConfig": {
47 | "access": "public"
48 | },
49 | "scripts": {
50 | "build": "tsc",
51 | "build:watch": "tsc -w",
52 | "lint": "eslint \"{src,test,scripts}/**/*.{ts,js}\" --fix",
53 | "prettier": "node ./scripts/prettier.js write-changed",
54 | "prettier-all": "node ./scripts/prettier.js write",
55 | "bundle": "cross-env rimraf dist && node scripts/bundle.js",
56 | "bundle:bench": "cross-env rimraf dist && node scripts/bundle.js bench",
57 | "test": "mocha \"test/**/*.ts\"",
58 | "test:single": "mocha",
59 | "test:watch": "npm run test -- --watch --watch-extensions ts",
60 | "test:verbose": "npm run test -- -R spec",
61 | "test:watch:verbose": "npm run test:watch -- -R spec",
62 | "prepare-nightly": "node scripts/bump-dev-version",
63 | "coverage": "cross-env TS_NODE_PROJECT=\"test/tsconfig.json\" nyc -n \"src/**/*.ts\" -e .ts -i ts-node/register -r text-summary -r lcov -r html npm test",
64 | "post_coverage": "cross-env cat ./coverage/lcov.info | coveralls"
65 | },
66 | "devDependencies": {
67 | "@types/mocha": "^8.0.3",
68 | "@types/node": "^14.6.0",
69 | "@typescript-eslint/eslint-plugin": "^3.10.1",
70 | "@typescript-eslint/parser": "^3.10.1",
71 | "chalk": "^4.1.0",
72 | "cross-env": "^7.0.2",
73 | "eslint": "^7.7.0",
74 | "eslint-plugin-import": "^2.22.0",
75 | "eslint-plugin-node": "^11.1.0",
76 | "glob": "^7.1.6",
77 | "husky": "^4.2.5",
78 | "mocha": "^8.1.1",
79 | "nyc": "^15.1.0",
80 | "path": "^0.12.7",
81 | "prettier": "^2.1.0",
82 | "rimraf": "^3.0.2",
83 | "rollup": "^2.26.5",
84 | "rollup-plugin-replace": "^2.2.0",
85 | "rollup-plugin-terser": "^7.0.0",
86 | "rollup-plugin-typescript2": "^0.27.2",
87 | "source-map-support": "^0.5.19",
88 | "test262-parser": "^2.2.0",
89 | "test262-parser-tests": "0.0.5",
90 | "ts-node": "^9.0.0",
91 | "tsconfig-paths": "^3.9.0",
92 | "typescript": "^4.0.2",
93 | "unicode-13.0.0": "^0.8.0"
94 | },
95 | "husky": {
96 | "hooks": {
97 | "pre-commit": "node ./scripts/prettier.js check-changed"
98 | }
99 | },
100 | "engines": {
101 | "node": ">=6.0.0"
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/test/scanner/bom.ts:
--------------------------------------------------------------------------------
1 | import * as t from 'assert';
2 | import { create } from '../../src/parser/core';
3 | import { skipMeta } from '../../src/scanner/';
4 |
5 | describe('Scanner - BOM', () => {
6 | function pass(name: string, opts: any) {
7 | it(name, () => {
8 | const parser = create(opts.source);
9 | skipMeta(parser, opts.source);
10 | t.deepEqual(
11 | {
12 | hasNext: parser.index < parser.source.length,
13 | line: parser.curLine,
14 | column: parser.index - parser.offset
15 | },
16 | {
17 | hasNext: opts.hasNext,
18 | line: opts.line,
19 | column: opts.column
20 | }
21 | );
22 | });
23 | }
24 | pass('skips nothing', {
25 | source: '',
26 | hasNext: false,
27 | line: 1,
28 | column: 0
29 | });
30 |
31 | pass('skips nothing before a lone exclamation', {
32 | source: '! foo',
33 | hasNext: true,
34 | line: 1,
35 | column: 0
36 | });
37 |
38 | pass('skips a BOM in an otherwise empty source', {
39 | source: '\uFFEF',
40 | hasNext: false,
41 | line: 1,
42 | column: 1
43 | });
44 |
45 | pass('skips nothing before a lone exclamation', {
46 | source: '! foo',
47 | hasNext: true,
48 | line: 1,
49 | column: 0
50 | });
51 |
52 | pass('skips a shebang+LF in an otherwise empty source', {
53 | source: '#!/foo/bar/baz -abc\n',
54 | hasNext: true,
55 | line: 1,
56 | column: 19
57 | });
58 |
59 | pass('skips a shebang+LF before a lone hash', {
60 | source: '#!/foo/bar/baz -abc\n# foo',
61 | hasNext: true,
62 | line: 1,
63 | column: 19
64 | });
65 |
66 | pass('skips a shebang+LF before a lone exclamation', {
67 | source: '#!/foo/bar/baz -abc\n! foo',
68 | hasNext: true,
69 | line: 1,
70 | column: 19
71 | });
72 |
73 | pass('skips a shebang+CR in an otherwise empty source', {
74 | source: '#!/foo/bar/baz -abc\r',
75 | hasNext: true,
76 | line: 1,
77 | column: 19
78 | });
79 |
80 | pass('skips a shebang+CR before an identifier', {
81 | source: '#!/foo/bar/baz -abc\rfoo',
82 | hasNext: true,
83 | line: 1,
84 | column: 19
85 | });
86 |
87 | pass('skips a shebang+CR before a lone hash', {
88 | source: '#!/foo/bar/baz -abc\r# foo',
89 | hasNext: true,
90 | line: 1,
91 | column: 19
92 | });
93 |
94 | pass('skips a shebang+CR before a lone exclamation', {
95 | source: '#!/foo/bar/baz -abc\r! foo',
96 | hasNext: true,
97 | line: 1,
98 | column: 19
99 | });
100 |
101 | pass('skips a shebang+CRLF before an identifier', {
102 | source: '#!/foo/bar/baz -abc\r\nfoo',
103 | hasNext: true,
104 | line: 1,
105 | column: 19
106 | });
107 |
108 | pass('skips a BOM+shebang+LF before an identifier', {
109 | source: '\uFFEF#!/foo/bar/baz -abc\nfoo',
110 | hasNext: true,
111 | line: 1,
112 | column: 20
113 | });
114 |
115 | pass('skips a BOM+shebang+LF before a lone hash', {
116 | source: '\uFFEF#!/foo/bar/baz -abc\n# foo',
117 | hasNext: true,
118 | line: 1,
119 | column: 20
120 | });
121 |
122 | pass('skips a BOM+shebang+CR before a lone exclamation', {
123 | source: '\uFFEF#!/foo/bar/baz -abc\r! foo',
124 | hasNext: true,
125 | line: 1,
126 | column: 20
127 | });
128 |
129 | pass('skips a BOM+shebang+paragraph separator before a lone exclamation', {
130 | source: '\uFFEF#!/foo/bar/baz -abc\u2029! foo',
131 | hasNext: true,
132 | line: 1,
133 | column: 20
134 | });
135 | });
136 |
--------------------------------------------------------------------------------
/test/parser/miscellaneous/simple-parameter-list.ts:
--------------------------------------------------------------------------------
1 | import { Context } from '../../../src/parser/common';
2 | import { parseRoot } from '../../../src/seafox';
3 | import * as t from 'assert';
4 |
5 | for (const arg of [
6 | // Array destructuring.
7 | '[]',
8 | '[]',
9 | '[a]',
10 | 'x, [a]',
11 | '[a, b]',
12 | '[a], x',
13 | // Array destructuring with defaults.
14 | '[a = 0]',
15 | '[a, b] = []',
16 | 'x, [a = 0]',
17 | '[a = 0], x',
18 |
19 | // Array destructuring with rest binding identifier.
20 | '[...a]',
21 | 'x, [...a]',
22 | '[...a], x',
23 | // Array destructuring with rest binding pattern.
24 | '[...[a]]',
25 | 'x, [...[a]]',
26 | '[...[a]], x',
27 |
28 | // Object destructuring.
29 | '{}',
30 | '{p: o}',
31 | 'x, {p: o}',
32 | '{p: o}, x',
33 |
34 | // Object destructuring with defaults.
35 | '{p: o = 0}',
36 | 'x, {p: o = 0}',
37 | '{p: o = 0}, x',
38 | '{ p, o } = {}',
39 | // Object destructuring with shorthand identifier form.
40 | '{o}',
41 | 'x, {o}',
42 | '{o}, x',
43 |
44 | // Object destructuring with CoverInitName.
45 | '{o = 0}',
46 | 'x, {o = 0}',
47 | '{o = 0}, x',
48 |
49 | // Object setter
50 | '{ set f(a = 1) }, x',
51 |
52 | // Default parameter.
53 | 'd = 0',
54 | 'x, d = 0',
55 | 'd = 0, x',
56 |
57 | // Rest parameter.
58 | '...rest',
59 | 'x, ...rest',
60 | '...x',
61 |
62 | // Rest parameter with array destructuring.
63 | '...[]',
64 | '...[a]',
65 | 'x, ...[]',
66 | 'x, ...[a]',
67 | // Rest parameter with object destructuring.
68 | '...{}',
69 | '...{p: o}',
70 | 'x, ...{}',
71 | 'x, ...{p: o}',
72 |
73 | // All non-simple cases combined.
74 | 'x, d = 123, [a], {p: 0}, ...rest',
75 |
76 | // Misc
77 | 'a, {b}',
78 | '{}',
79 | '[]',
80 | '[{}]',
81 | '{a}',
82 | 'a, {b}',
83 | 'a, b, {c, d, e}',
84 | 'a = b',
85 | 'a, b, c = 1',
86 | '...args',
87 | 'a, b, ...rest',
88 | '[a, b, ...rest]',
89 | '{ a = {} }',
90 | '{ a } = { b: true }'
91 | ]) {
92 | it(`function f(${arg}) { "use strict"; }`, () => {
93 | t.throws(() => {
94 | parseRoot(`function f(${arg}) { "use strict"; }`, Context.Empty);
95 | });
96 | });
97 |
98 | it(`async function *f(${arg}) { "use strict"; }`, () => {
99 | t.throws(() => {
100 | parseRoot(`async function *f(${arg}) { "use strict"; }`, Context.Empty);
101 | });
102 | });
103 |
104 | it(`void function(${arg}) { "use strict"; };`, () => {
105 | t.throws(() => {
106 | parseRoot(`void function(${arg}) { "use strict"; };`, Context.Empty);
107 | });
108 | });
109 |
110 | it(`(class { constructor(${arg}) { "use strict"; } });`, () => {
111 | t.throws(() => {
112 | parseRoot(`(class { constructor(${arg}) { "use strict"; } });`, Context.Empty);
113 | });
114 | });
115 |
116 | it(`({ set m(${arg}) { "use strict"; } });`, () => {
117 | t.throws(() => {
118 | parseRoot(`({ set m(${arg}) { "use strict"; } });`, Context.Empty);
119 | });
120 | });
121 |
122 | it(`class C { async m(${arg}) { "use strict"; } }`, () => {
123 | t.throws(() => {
124 | parseRoot(`class C { async m(${arg}) { "use strict"; } }`, Context.Empty);
125 | });
126 | });
127 |
128 | it(`class C { *m(${arg}) { "use strict"; } }`, () => {
129 | t.throws(() => {
130 | parseRoot(`class C { *m(${arg}) { "use strict"; } }`, Context.Empty);
131 | });
132 | });
133 |
134 | it(`class C { async m(${arg}) { "use strict"; } }`, () => {
135 | t.throws(() => {
136 | parseRoot(`class C { async m(${arg}) { "use strict"; } }`, Context.Empty);
137 | });
138 | });
139 |
140 | it(`(${arg}) => { "use strict"; };`, () => {
141 | t.throws(() => {
142 | parseRoot(`(${arg}) => { "use strict"; };`, Context.Empty);
143 | });
144 | });
145 |
146 | it(`async (${arg}) => { "use strict"; };`, () => {
147 | t.throws(() => {
148 | parseRoot(`async (${arg}) => { "use strict"; };`, Context.Empty);
149 | });
150 | });
151 | }
152 |
--------------------------------------------------------------------------------
/src/scanner/regexp.ts:
--------------------------------------------------------------------------------
1 | import { Chars, isIdentifierPart, CharFlags, CharTypes } from './';
2 | import { ParserState } from '../parser/common';
3 | import { Token } from '../token';
4 | import { report, Errors } from '../errors';
5 |
6 | /**
7 | * Scans regular expression
8 | *
9 | * @param parser Parser object
10 | * @param context Context masks
11 | */
12 |
13 | export function scanRegularExpression(parser: ParserState, source: string, i: number): Token {
14 | const enum RegexState {
15 | Empty = 0,
16 | Escape = 0x1,
17 | Class = 0x2
18 | }
19 | const bodyStart = i;
20 | // Scan: ('/' | '/=') RegularExpressionBody '/' RegularExpressionFlags
21 | let preparseState = RegexState.Empty;
22 |
23 | while (true) {
24 | const ch = source.charCodeAt(i);
25 | i++;
26 | if (preparseState & RegexState.Escape) {
27 | preparseState &= ~RegexState.Escape;
28 | } else {
29 | if (ch <= 0x5e) {
30 | if (ch === Chars.Slash) {
31 | if (!preparseState) break;
32 | } else if (ch === Chars.Backslash) {
33 | preparseState |= RegexState.Escape;
34 | } else if (ch === Chars.LeftBracket) {
35 | preparseState |= RegexState.Class;
36 | } else if (ch === Chars.RightBracket) {
37 | preparseState &= RegexState.Escape;
38 | } else if ((CharTypes[ch] & CharFlags.LineTerminator) === CharFlags.LineTerminator) {
39 | report(parser, Errors.UnterminatedRegExp);
40 | }
41 | } else if ((ch & ~1) === Chars.LineSeparator) {
42 | report(parser, Errors.UnterminatedRegExp);
43 | }
44 | }
45 |
46 | if (i >= parser.length) {
47 | report(parser, Errors.UnterminatedRegExp);
48 | }
49 | }
50 |
51 | const bodyEnd = i - 1;
52 |
53 | const enum RegexFlags {
54 | Empty = 0b00000000000000000000000000000000,
55 | Global = 0b00000000000000000000000000000001,
56 | IgnoreCase = 0b00000000000000000000000000000010,
57 | Multiline = 0b00000000000000000000000000000100,
58 | Unicode = 0b00000000000000000000000000001000,
59 | Sticky = 0b00000000000000000000000000010000,
60 | DotAll = 0b00000000000000000000000000100000
61 | }
62 |
63 | let mask = RegexFlags.Empty;
64 |
65 | const flagStart = i;
66 |
67 | let char = source.charCodeAt(i);
68 |
69 | while (isIdentifierPart(char)) {
70 | switch (char) {
71 | case Chars.LowerG:
72 | if (mask & RegexFlags.Global) report(parser, Errors.DuplicateRegExpFlag, 'g');
73 | mask |= RegexFlags.Global;
74 | break;
75 |
76 | case Chars.LowerI:
77 | if (mask & RegexFlags.IgnoreCase) report(parser, Errors.DuplicateRegExpFlag, 'i');
78 | mask |= RegexFlags.IgnoreCase;
79 | break;
80 |
81 | case Chars.LowerM:
82 | if (mask & RegexFlags.Multiline) report(parser, Errors.DuplicateRegExpFlag, 'm');
83 | mask |= RegexFlags.Multiline;
84 | break;
85 |
86 | case Chars.LowerU:
87 | if (mask & RegexFlags.Unicode) report(parser, Errors.DuplicateRegExpFlag, 'g');
88 | mask |= RegexFlags.Unicode;
89 | break;
90 |
91 | case Chars.LowerY:
92 | if (mask & RegexFlags.Sticky) report(parser, Errors.DuplicateRegExpFlag, 'y');
93 | mask |= RegexFlags.Sticky;
94 | break;
95 |
96 | case Chars.LowerS:
97 | if (mask & RegexFlags.DotAll) report(parser, Errors.DuplicateRegExpFlag, 's');
98 | mask |= RegexFlags.DotAll;
99 | break;
100 |
101 | default:
102 | report(parser, Errors.UnexpectedTokenRegExpFlag);
103 | }
104 |
105 | i++;
106 | char = source.charCodeAt(i);
107 | }
108 |
109 | const flags = source.slice(flagStart, i);
110 |
111 | const pattern = source.slice(bodyStart, bodyEnd);
112 |
113 | parser.tokenRegExp = { pattern, flags };
114 |
115 | parser.index = i;
116 |
117 | try {
118 | parser.tokenValue = new RegExp(pattern, flags);
119 | } catch (e) {
120 | report(parser, Errors.Unexpected);
121 | }
122 |
123 | return Token.RegularExpression;
124 | }
125 |
--------------------------------------------------------------------------------
/src/seafox.ts:
--------------------------------------------------------------------------------
1 | import { Context, OnToken, isFunction } from './parser/common';
2 | import * as Types from './parser/types';
3 | import { nextToken } from './scanner/scan';
4 | import { skipMeta } from './scanner/';
5 | import { parseModuleItemList } from './parser/module';
6 | import { parseStatementList } from './parser/statements';
7 | import { create } from './parser/core';
8 | import { createTopLevelScope } from './parser/scope';
9 |
10 | /**
11 | * The parser options.
12 | */
13 | export interface Options {
14 | // Allow parsing using Module as the goal symbol
15 | module?: boolean;
16 | // Enable stage 3 support (ESNext)
17 | next?: boolean;
18 | // Disable web compatibility
19 | disableWebCompat?: boolean;
20 | // Enable line/column location information start and end offsets to each node
21 | loc?: boolean;
22 | // Attach raw property to each literal and identifier node
23 | raw?: boolean;
24 | // Enabled directives
25 | directives?: boolean;
26 | // Allow return in the global scope
27 | globalReturn?: boolean;
28 | // Enable implied strict mode
29 | impliedStrict?: boolean;
30 | // Adds a source attribute in every node’s loc object when the locations option is `true`
31 | source?: string;
32 | // Enable non-standard parenthesized expression node
33 | preserveParens?: boolean;
34 | // Enable lexical analysis
35 | onToken?: OnToken;
36 | }
37 |
38 | export function parseRoot(source: string, context: Context, options?: Options): Types.Program {
39 | let onToken: OnToken = void 0;
40 | if (options !== undefined) {
41 | if (options.module) context |= Context.Module | Context.Strict;
42 | if (options.next) context |= Context.OptionsNext;
43 | if (options.loc) context |= Context.OptionsLoc;
44 | if (options.disableWebCompat) context |= Context.OptionsDisableWebCompat;
45 | if (options.directives) context |= Context.OptionsDirectives | Context.OptionsRaw;
46 | if (options.raw) context |= Context.OptionsRaw;
47 | if (options.globalReturn) context |= Context.OptionsGlobalReturn;
48 | if (options.preserveParens) context |= Context.OptionsPreserveParens;
49 | if (options.impliedStrict) context |= Context.Strict;
50 |
51 | // Lexical analysis (tokenization)
52 | if (options.onToken) {
53 | if (!isFunction(options.onToken)) throw 'onToken option can only be a function type';
54 | onToken = options.onToken;
55 | context |= Context.OptionsOnToken;
56 | }
57 | }
58 |
59 | // Initialize parser state
60 | const parser = create(source, onToken);
61 |
62 | // See: https://github.com/tc39/proposal-hashbang
63 | skipMeta(parser, source);
64 |
65 | nextToken(parser, context, /* allowRegExp */ 1);
66 |
67 | const scope = createTopLevelScope();
68 |
69 | // https://tc39.es/ecma262/#sec-scripts
70 | // https://tc39.es/ecma262/#sec-modules
71 |
72 | const sourceType: 'module' | 'script' = context & Context.Module ? 'module' : 'script';
73 |
74 | const body =
75 | sourceType === 'module' ? parseModuleItemList(parser, context, scope) : parseStatementList(parser, context, scope);
76 |
77 | return (context & 0b00000000000000000000000000000010) === 0b00000000000000000000000000000010
78 | ? {
79 | type: 'Program',
80 | sourceType,
81 | body,
82 | start: 0,
83 | end: source.length,
84 | loc: {
85 | start: {
86 | line: 1,
87 | column: 0
88 | },
89 | end: {
90 | line: parser.curLine,
91 | column: parser.index - parser.offset
92 | }
93 | }
94 | }
95 | : {
96 | type: 'Program',
97 | sourceType,
98 | body
99 | };
100 | }
101 |
102 | /**
103 | * Parse a script, optionally with various options.
104 | */
105 | export function parseScript(source: string, options?: Options): Types.Program {
106 | return parseRoot(source, Context.InGlobal, options);
107 | }
108 |
109 | /**
110 | * Parse a module, optionally with various options.
111 | */
112 | export function parseModule(source: string, options?: Options): Types.Program {
113 | return parseRoot(source, Context.Strict | Context.Module | Context.InGlobal, options);
114 | }
115 |
116 | /**
117 | * Parse a module or a script, optionally with various options.
118 | */
119 | export function parse(source: string, options?: Options): Types.Program {
120 | return parseRoot(source, Context.InGlobal, options);
121 | }
122 |
123 | export const version = '1.7.1';
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Seafox
2 |
3 | A blazing fast 100% spec compliant, self-hosted javascript parser written in Typescript.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ## Features
16 |
17 | * Conforms to the standard ECMAScript® 2021 (ECMA-262 11th Edition) language specification
18 | * Support for additional ECMAScript features for Web Browsers
19 | * Optionally track syntactic node locations
20 | * Emits an ESTree-compatible abstract syntax tree
21 | * Lexical analysis
22 | * No backtracking
23 | * Low memory usage
24 | * Insane performance both on desktop computers and handheld devices
25 | * Twice as fast as other Javascript parsers
26 | * Very well tested (~33 000 unit tests with full code coverage)
27 | * Lightweight - ~84 KB minified
28 |
29 | ## Installation
30 |
31 | ```sh
32 | npm install seafox --save-dev
33 | ```
34 |
35 | ## API
36 |
37 | Seafox generates `AST` according to [ESTree AST format](https://github.com/estree/estree), and can be used to perform [syntactic analysis](https://en.wikipedia.org/wiki/Parsing) (parsing) or [lexical analysis](https://en.wikipedia.org/wiki/Lexical_analysis) (tokenization) of a JavaScript program, and with `ES2015` and later a JavaScript program can be either [a script or a module](https://tc39.github.io/ecma262/index.html#sec-ecmascript-language-scripts-and-modules).
38 |
39 | The `parse` method exposed by `Seafox` takes an optional `options` object which allows you to specify whether to parse in [`script`](https://tc39.github.io/ecma262/#sec-parse-script) mode (the default) or in [`module`](https://tc39.github.io/ecma262/#sec-parsemodule) mode.
40 |
41 |
42 | This is the available options:
43 |
44 | ```js
45 | {
46 | // Allow parsing using Module as the goal symbol
47 | module?: boolean;
48 |
49 | // The flag to enable start and end offsets and line/column location information to each node
50 | loc: false;
51 |
52 | // Disable web compatibility
53 | disableWebCompat: false;
54 |
55 | // The flag to attach raw property to each literal and identifier node
56 | raw: false;
57 |
58 | // Enabled directives
59 | directives: false;
60 |
61 | // The flag to allow return in the global scope
62 | globalReturn: false;
63 |
64 | // The flag to enable implied strict mode
65 | impliedStrict: false;
66 |
67 | // Enable non-standard parenthesized expression node
68 | preserveParens: false;
69 |
70 | // Allows token extraction. Accepts only a function
71 | onToken: function() {}
72 | }
73 | ```
74 |
75 | Example usage:
76 |
77 | ```ts
78 |
79 | import { parseScript, parseModule, parse } from './seafox';
80 |
81 | parseScript('({x: [y] = 0} = 1)');
82 |
83 | parseModule('({x: [y] = 0} = 1)', { directives: true, raw: true });
84 |
85 | parse('({x: [y] = 0} = 1)', { module: true });
86 |
87 | parse('({x: [y] = 0} = 1)');
88 |
89 | ```
90 |
91 | # Lexical analysis
92 |
93 | Lexical analysis can only be done during parsing and accepts only a function type as the option
94 |
95 | ```ts
96 | parseScript('foo = bar', { onToken: () => {}});
97 | ```
98 | The callback function have 4 arguments.
99 |
100 | | Arguments | Description |
101 | | ----------- | ------------------------------------------------------------ |
102 | | `token` | The token to be extracted |
103 | | `value` | Value of the extracted token |
104 | | `start` | Start position of the extracted token |
105 | | `end` | End position of the extracted token |
106 |
107 | The `loc` option needs to be enabled for `start` and `end`. Otherwise this values will be set to `undefined`
108 |
109 |
110 | # Performance
111 |
112 | Seafox is developed for performance and low memory usage, and the parser is about 2x - 4x faster than all other javascript parsers.
113 |
--------------------------------------------------------------------------------
/src/scanner/identifier.ts:
--------------------------------------------------------------------------------
1 | import { ParserState } from '../parser/common';
2 | import { Token, descKeywordTable } from '../token';
3 | import { report, Errors } from '../errors';
4 | import { Chars, isIdentifierPart, CharTypes, CharFlags, fromCodePoint, toHex, unicodeLookup } from './';
5 |
6 | export function scanIdentifierOrKeyword(parser: ParserState, source: string, char: number, maybeKeyword: 0 | 1): Token {
7 | while ((CharTypes[char] & 0b00000000000000000000000000000101) > 0) {
8 | char = source.charCodeAt(++parser.index);
9 | }
10 |
11 | const value = source.slice(parser.start, parser.index);
12 |
13 | if (char > Chars.UpperZ) return scanIdentifierSlowPath(parser, source, value, maybeKeyword);
14 |
15 | parser.tokenValue = value;
16 |
17 | const token: Token | undefined = descKeywordTable[value];
18 |
19 | return token === void 0 ? Token.Identifier : token;
20 | }
21 |
22 | export function scanIdentifierSlowPath(parser: ParserState, source: string, value: string, maybeKeyword: 0 | 1): Token {
23 | let start = parser.index;
24 | let char = source.charCodeAt(parser.index);
25 | let code: number | null = null;
26 |
27 | while (parser.index < parser.length) {
28 | if (char === Chars.Backslash) {
29 | value += source.slice(start, parser.index);
30 | parser.containsEscapes = 1;
31 | code = scanUnicodeEscape(parser, source);
32 | if (!isIdentifierPart(code)) report(parser, Errors.InvalidUnicodeEscapeSequence);
33 | maybeKeyword = 1;
34 | value += fromCodePoint(code);
35 | start = parser.index;
36 | } else {
37 | if ((char & 0xfc00) === 0xd800) {
38 | if ((source.charCodeAt(parser.index + 1) & 0xfc00) === 0xdc00) {
39 | char = 0x10000 + ((char & 0x3ff) << 10) + (source.charCodeAt(parser.index + 1) & 0x3ff);
40 | if (((unicodeLookup[(char >>> 5) + 0] >>> char) & 31 & 1) === 0) {
41 | report(parser, Errors.IllegalCaracter, fromCodePoint(char));
42 | }
43 | parser.index++;
44 | }
45 | }
46 | if (!isIdentifierPart(char)) break;
47 | parser.index++;
48 | }
49 | char = source.charCodeAt(parser.index);
50 | }
51 |
52 | value += source.slice(start, parser.index);
53 |
54 | const length = value.length;
55 |
56 | parser.tokenValue = value;
57 |
58 | if (maybeKeyword && length >= 2 && length <= 11) {
59 | const token: Token | undefined = descKeywordTable[parser.tokenValue];
60 |
61 | if (token !== void 0) {
62 | return parser.containsEscapes === 0
63 | ? token
64 | : (token & Token.Contextual) === Token.Contextual
65 | ? token
66 | : token | Token.EscapedKeyword;
67 | }
68 | }
69 |
70 | return Token.Identifier;
71 | }
72 | /**
73 | * Scans unicode identifier
74 | *
75 | * @param parser Parser object
76 | */
77 | export function scanUnicodeEscape(parser: ParserState, source: string): number {
78 | // Check for Unicode escape of the form '\uXXXX'
79 | // and return code point value if valid Unicode escape is found.
80 | if (source.charCodeAt(parser.index + 1) !== Chars.LowerU) {
81 | report(parser, Errors.InvalidUnicodeEscapeSequence);
82 | }
83 |
84 | let code = 0;
85 |
86 | let char = source.charCodeAt((parser.index += 2));
87 |
88 | if (char === Chars.LeftBrace) {
89 | // '\\u{'
90 | if (parser.index === source.length) report(parser, Errors.InvalidHexEscapeSequence);
91 |
92 | // \u{N}
93 | // The first digit is required, so handle it *out* of the loop.
94 | let digit = toHex(source.charCodeAt(++parser.index));
95 |
96 | if (digit < 0) report(parser, Errors.InvalidHexEscapeSequence);
97 |
98 | while (digit >= 0) {
99 | code = (code << 4) | digit;
100 | // Check this early to avoid `code` wrapping to a negative on overflow (which is
101 | // reserved for abnormal conditions).
102 | if (code > Chars.LastUnicodeChar) report(parser, Errors.UnicodeOverflow);
103 | digit = toHex((char = source.charCodeAt(++parser.index)));
104 | }
105 |
106 | // At least 4 characters have to be read
107 | if (code < 0 || char !== Chars.RightBrace) report(parser, Errors.InvalidHexEscapeSequence);
108 |
109 | parser.index++; // consumes '}'
110 |
111 | return code;
112 | }
113 |
114 | // \uNNNN
115 |
116 | const first =
117 | (toHex(char) << 12) |
118 | (toHex(source.charCodeAt(parser.index + 1)) << 8) |
119 | (toHex(source.charCodeAt(parser.index + 2)) << 4);
120 |
121 | const ch4 = source.charCodeAt(parser.index + 3);
122 | if ((CharTypes[ch4] & CharFlags.Hex) === 0) report(parser, Errors.InvalidHexEscapeSequence);
123 |
124 | parser.index += 4;
125 |
126 | return first | toHex(ch4);
127 | }
128 |
129 | export function scanUnicodeEscapeIdStart(parser: ParserState, source: string): Token {
130 | const cookedChar = scanUnicodeEscape(parser, source);
131 | if (isIdentifierPart(cookedChar)) {
132 | parser.containsEscapes = 1;
133 | return scanIdentifierSlowPath(parser, source, fromCodePoint(cookedChar), /* maybeKeyword */ 1);
134 | }
135 | parser.index++; // skip: '\'
136 | report(parser, Errors.InvalidUnicodeEscapeSequence);
137 | }
138 |
--------------------------------------------------------------------------------
/src/parser/scope.ts:
--------------------------------------------------------------------------------
1 | import { ParserState, BindingKind, Origin, Context } from './common';
2 | import { Errors, report } from '../errors';
3 |
4 | /**
5 | * Lexical scope interface
6 | */
7 | export interface ScopeState {
8 | parent: ScopeState | undefined;
9 | type: ScopeKind;
10 | declared: string[];
11 | scopeError?: ScopeError | null;
12 | }
13 |
14 | /** Scope error interface */
15 | export interface ScopeError {
16 | type: Errors;
17 | params: string[];
18 | index: number;
19 | line: number;
20 | column: number;
21 | }
22 |
23 | export const enum ScopeKind {
24 | None = 0b00000000000000000000000000000000,
25 | Block = 0b00000000000000000000000000000010,
26 | CatchIdentifier = 0b00000000000000000000000000000100,
27 | CatchBlock = 0b00000000000000000000000000100000,
28 | FunctionBody = 0b00000000000000000000000001000000,
29 | FunctionRoot = 0b00000000000000000000000010000000,
30 | ArrowParams = 0b00000000000000000000001000000000
31 | }
32 |
33 | export function createNestedBlockScope(type: ScopeKind): ScopeState {
34 | return {
35 | type,
36 | parent: {
37 | type,
38 | parent: void 0,
39 | declared: []
40 | },
41 | declared: [],
42 | scopeError: void 0
43 | };
44 | }
45 |
46 | export function createTopLevelScope(): ScopeState {
47 | return {
48 | parent: void 0,
49 | declared: [],
50 | type: ScopeKind.Block
51 | };
52 | }
53 | export function createParentScope(parent: ScopeState, type: ScopeKind): ScopeState {
54 | return {
55 | parent,
56 | declared: [],
57 | type,
58 | scopeError: void 0
59 | };
60 | }
61 | export function createArrowScope(): ScopeState {
62 | return {
63 | type: ScopeKind.ArrowParams,
64 | parent: {
65 | parent: void 0,
66 | declared: [],
67 | type: ScopeKind.Block
68 | },
69 | declared: [],
70 | scopeError: void 0
71 | };
72 | }
73 |
74 | export function addVarOrBlock(
75 | parser: ParserState,
76 | context: Context,
77 | scope: ScopeState,
78 | name: string,
79 | kind: BindingKind,
80 | origin: Origin
81 | ) {
82 | if (kind & BindingKind.Variable) {
83 | addVarName(parser, context, scope, name, kind);
84 | } else {
85 | addBlockName(parser, context, scope, name, kind, origin);
86 | }
87 |
88 | if (origin & Origin.Export) declareUnboundVariable(parser, name);
89 | }
90 |
91 | export function declareUnboundVariable(parser: ParserState, name: string): void {
92 | if (parser.exportedNames !== void 0 && name !== '') {
93 | if (parser.exportedNames['#' + name]) {
94 | report(parser, Errors.DuplicateExportBinding, name);
95 | }
96 | parser.exportedNames['#' + name] = 1;
97 | }
98 | }
99 |
100 | /**
101 | * Appends a name to the `ExportedBindings` of the `ExportsList`,
102 | *
103 | * @see [Link](https://tc39.es/ecma262/$sec-exports-static-semantics-exportedbindings)
104 | *
105 | * @param parser Parser object
106 | * @param name Exported binding name
107 | */
108 | export function addBindingToExports(parser: ParserState, name: string): void {
109 | if (parser.exportedBindings !== void 0 && name !== '') {
110 | parser.exportedBindings['#' + name] = 1;
111 | }
112 | }
113 |
114 | export function addVarName(
115 | parser: ParserState,
116 | context: Context,
117 | curScope: ScopeState,
118 | name: string,
119 | kind: BindingKind
120 | ): void {
121 | let scope: any = curScope;
122 | let value: any;
123 |
124 | do {
125 | value = scope.declared['#' + name];
126 | if (value) {
127 | if (
128 | (value & 0b00000000000000000000000011110100) > 0 &&
129 | ((context & 0b00000000000000000000010000010000) > 0 ||
130 | (kind & ScopeKind.FunctionRoot) === 0 ||
131 | (value & ScopeKind.FunctionRoot) === 0)
132 | ) {
133 | report(parser, Errors.DuplicateBinding, name);
134 | }
135 |
136 | if (
137 | (value & 0b00000000000000000000001100000000) > 0 &&
138 | ((context & 0b00000000000000000000010000010000) > 0 || (value & ScopeKind.ArrowParams) === 0)
139 | ) {
140 | report(parser, Errors.DuplicateBinding, name);
141 | }
142 | }
143 | scope.declared['#' + name] = kind;
144 |
145 | scope = scope.parent;
146 | } while (scope && (scope.type & ScopeKind.FunctionRoot) === 0);
147 | }
148 |
149 | /**
150 | * Adds block scoped binding
151 | *
152 | * @param parser Parser state
153 | * @param context Context masks
154 | * @param scope Scope state
155 | * @param name Binding name
156 | * @param type Binding kind
157 | * @param origin Binding Origin
158 | */
159 | export function addBlockName(
160 | parser: ParserState,
161 | context: Context,
162 | scope: any,
163 | name: string,
164 | kind: BindingKind,
165 | origin: Origin
166 | ) {
167 | if (scope === void 0) return;
168 |
169 | const value = scope.declared['#' + name];
170 | if (value) {
171 | if ((value & 0b00000000000000000000000000001000) === 0) {
172 | if ((kind & 0b00000000000000000000000000000001) > 0) {
173 | scope.scopeError = {
174 | type: Errors.DuplicateBinding,
175 | params: name,
176 | index: parser.index,
177 | line: parser.line,
178 | column: parser.column
179 | };
180 | } else if (
181 | (context & 0b00000000000000000000000000010000) > 0 ||
182 | (value & 0b00000000000000000000000000000100) === 0 ||
183 | (origin & Origin.BlockStatement) === 0
184 | ) {
185 | report(parser, Errors.DuplicateBinding, name);
186 | }
187 | }
188 | }
189 | const parent = scope.parent;
190 |
191 | if (
192 | (scope.type & ScopeKind.FunctionBody) > 0 &&
193 | parent.declared['#' + name] &&
194 | (parent.declared['#' + name] & 0b00000000000000000000000000001000) === 0
195 | ) {
196 | report(parser, Errors.DuplicateBinding, name);
197 | }
198 |
199 | if (
200 | (scope.type & ScopeKind.CatchBlock) > 0 &&
201 | (parent.declared['#' + name] & 0b00000000000000000000001100000000) > 0
202 | ) {
203 | report(parser, Errors.ShadowedCatchClause, name);
204 | }
205 |
206 | scope.declared['#' + name] = kind;
207 | }
208 |
--------------------------------------------------------------------------------
/scripts/generate-unicode.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const UnicodeCodeCount = 0x110000; /* codes */
4 | const VectorSize = Uint32Array.BYTES_PER_ELEMENT * 8;
5 | const VectorMask = VectorSize - 1;
6 | const VectorBitCount = 32 - Math.clz32(VectorMask);
7 | const VectorByteSize = UnicodeCodeCount / VectorSize;
8 |
9 | const DataInst = {
10 | Empty: 0x0,
11 | Many: 0x1,
12 | Link: 0x2
13 | };
14 |
15 | exports.DataInst = DataInst;
16 | exports.compressorCreate = compressorCreate;
17 |
18 | function compressorCreate() {
19 | return {
20 | result: [],
21 | lookupLocs: Object.create(null),
22 | lookupIn: Object.create(null),
23 | lookup: [],
24 | count: 0,
25 | prev: 0,
26 | mask: DataInst.Empty,
27 | size: 0
28 | };
29 | }
30 |
31 | exports.compressorSend = compressorSend;
32 |
33 | function compressorSend(state, code) {
34 | state.size++;
35 |
36 | if (state.count === 0) {
37 | state.prev = code;
38 | state.count++;
39 | return;
40 | }
41 |
42 | if (state.prev === code) {
43 | state.mask |= DataInst.Many;
44 | state.count++;
45 | return;
46 | }
47 |
48 | if (state.prev === 0) {
49 | state.result.push(-state.count);
50 | } else {
51 | state.result.push(state.mask);
52 |
53 | if (state.mask & DataInst.Link) {
54 | state.result.push(state.lookupIn[state.prev]);
55 | } else {
56 | if (state.prev >= 10) state.lookupLocs[state.prev] = state.result.length;
57 | state.result.push(state.prev);
58 | }
59 |
60 | if (state.mask & DataInst.Many) state.result.push(state.count);
61 | }
62 |
63 | state.prev = code;
64 | state.mask = DataInst.Empty;
65 | state.count = 1;
66 | const loc = state.lookupLocs[code];
67 |
68 | if (loc == null) return;
69 |
70 | state.mask |= DataInst.Link;
71 |
72 | if (loc !== 0) {
73 | state.lookupLocs[code] = 0;
74 | state.result[loc - 1] |= DataInst.Link;
75 | state.result[loc] = state.lookup.length;
76 | state.lookupIn[code] = state.lookup.length;
77 | state.lookup.push(code);
78 | }
79 | }
80 |
81 | exports.compressorEnd = compressorEnd;
82 |
83 | function compressorEnd(state) {
84 | if (state.prev === 0) {
85 | state.result.push(-state.count);
86 | } else {
87 | state.result.push(state.mask);
88 |
89 | if (state.mask & DataInst.Link) {
90 | state.result.push(state.lookupIn[state.prev]);
91 | } else {
92 | state.result.push(state.prev);
93 | }
94 |
95 | if (state.mask & DataInst.Many) state.result.push(state.count);
96 | }
97 | }
98 |
99 | exports.decompress = decompress;
100 |
101 | function decompress(compressed) {
102 | return new Function(`return ${makeDecompress(compressed)}`)();
103 | }
104 |
105 | const makeDecompress = compressed => `((compressed, lookup) => {
106 | const result = new Uint32Array(${compressed.size})
107 | let index = 0;
108 | let subIndex = 0
109 | while (index < ${compressed.result.length}) {
110 | const inst = compressed[index++]
111 | if (inst < 0) {
112 | subIndex -= inst
113 | } else {
114 | let code = compressed[index++]
115 | if (inst & ${DataInst.Link}) code = lookup[code]
116 | if (inst & ${DataInst.Many}) {
117 | result.fill(code, subIndex, subIndex += compressed[index++])
118 | } else {
119 | result[subIndex++] = code
120 | }
121 | }
122 | }
123 | return result
124 | })(
125 | [${compressed.result}],
126 | [${compressed.lookup}]
127 | )`;
128 |
129 | exports.generate = generate;
130 |
131 | async function generate(opts) {
132 | await opts.write(`// Unicode v. 13 support
133 | /*eslint-disable*/
134 | `);
135 |
136 | const exportKeys = Object.keys(opts.exports);
137 | const compress = compressorCreate();
138 |
139 | for (const [index, exported] of exportKeys.entries()) {
140 | const codes = new Uint32Array(VectorByteSize);
141 | const items = opts.exports[exported];
142 |
143 | for (const list of items) {
144 | for (const item of list) {
145 | codes[item >>> VectorBitCount] |= 1 << (item & VectorMask);
146 | }
147 | }
148 |
149 | for (const code of codes) {
150 | compressorSend(compress, code);
151 | }
152 |
153 | await opts.write(`
154 | function ${exported}(code${opts.eval ? '' : ':number'}) {
155 | return (unicodeLookup[(code >>> ${VectorBitCount}) + ${index * VectorByteSize}] >>> code & ${VectorMask} & 1) !== 0
156 | }`);
157 | }
158 |
159 | compressorEnd(compress);
160 |
161 | await opts.write(`
162 | export const unicodeLookup = ${makeDecompress(compress)}
163 | ${opts.eval ? 'return' : 'export'} {${Object.keys(opts.exports)}};
164 | `);
165 | }
166 |
167 | if (require.main === module) {
168 | const path = require('path');
169 | const load = name => {
170 | const mod = require.resolve(`unicode-13.0.0/${name}/code-points`);
171 | const list = require(mod);
172 | delete require.cache[mod];
173 | return list;
174 | };
175 |
176 | const stream = require('fs').createWriteStream(path.resolve(__dirname, '../src/unicode.ts'));
177 |
178 | generate({
179 | write: str =>
180 | new Promise((resolve, reject) => {
181 | stream.write(str, err => (err != null ? reject(err) : resolve()));
182 | }),
183 | exports: {
184 | isIDContinue: [load('Binary_Property/ID_Continue')],
185 | isIDStart: [load('Binary_Property/ID_Start')],
186 | isLineTerminator: [[0x0d, 0x0a, 0x2028, 0x2029]],
187 | isWhiteSpace: [
188 | [
189 | 0x0d,
190 | 0x0a,
191 | 0x2028,
192 | 0x2029,
193 | 0x00a0,
194 | 0x1680,
195 | 0x2000,
196 | 0x2001,
197 | 0x2002,
198 | 0x2003,
199 | 0x2004,
200 | 0x2005,
201 | 0x2006,
202 | 0x2007,
203 | 0x2008,
204 | 0x2009,
205 | 0x200a,
206 | 0x202f,
207 | 0x205f,
208 | 0x3000,
209 | 0xfeff
210 | ]
211 | ],
212 | mustEscape: [load('General_Category/Other'), load('General_Category/Separator')]
213 | }
214 | }).catch(e =>
215 | process.nextTick(() => {
216 | throw e;
217 | })
218 | );
219 | }
220 |
--------------------------------------------------------------------------------
/src/scanner/string.ts:
--------------------------------------------------------------------------------
1 | import { ParserState, Context, Flags } from '../parser/common';
2 | import { Chars, toHex, fromCodePoint, readNext, CharTypes, CharFlags } from './';
3 | import { Token } from '../token';
4 | import { Errors, report } from '../errors';
5 |
6 | export const enum Escape {
7 | Empty = -1,
8 | StrictOctal = -2,
9 | EightOrNine = -3,
10 | InvalidHex = -4,
11 | OutOfRange = -5,
12 | MissingCurlyBrace = -6
13 | }
14 |
15 | export function scanStringLiteral(parser: ParserState, context: Context, source: string, quote: number): Token {
16 | let char = readNext(parser);
17 | let res = '';
18 | let start = parser.index;
19 |
20 | while (char !== quote) {
21 | if (char <= 0x7e) {
22 | if (char === Chars.Backslash) {
23 | res += source.slice(start, parser.index);
24 | char = readNext(parser);
25 | const code = scanEscapeSequence(parser, context, source, char);
26 | if (code <= 0) handleStringError(parser, code as Escape, /* isTemplate */ 0);
27 | res += fromCodePoint(code);
28 | start = parser.index;
29 | char = source.charCodeAt(parser.index);
30 | } else {
31 | char = readNext(parser);
32 | if ((CharTypes[char] & CharFlags.LineTerminator) === CharFlags.LineTerminator) {
33 | report(parser, Errors.UnterminatedString);
34 | }
35 | }
36 | } else {
37 | char = readNext(parser);
38 | if ((char & ~1) === Chars.LineSeparator) {
39 | parser.index++;
40 | parser.offset = parser.index;
41 | parser.curLine++;
42 | }
43 | }
44 | }
45 |
46 | res += source.slice(start, parser.index);
47 | parser.index++; // skip the closing quote
48 | parser.tokenValue = res;
49 | return Token.StringLiteral;
50 | }
51 |
52 | export function scanEscapeSequence(parser: ParserState, context: Context, source: string, first: number): number {
53 | let ch = source.charCodeAt(++parser.index);
54 |
55 | switch (first) {
56 | case Chars.LowerB:
57 | return Chars.Backspace;
58 | case Chars.LowerF:
59 | return Chars.FormFeed;
60 | case Chars.LowerR:
61 | return Chars.CarriageReturn;
62 | case Chars.LowerN:
63 | return Chars.LineFeed;
64 | case Chars.LowerT:
65 | return Chars.Tab;
66 | case Chars.LowerV:
67 | return Chars.VerticalTab;
68 |
69 | case Chars.LowerU: {
70 | // Accept both \uxxxx and \u{xxxxxx}. In the latter case, the number of
71 | // hex digits between { } is arbitrary. \ and u have already been scanned.
72 | let code = 0;
73 | if (ch === Chars.LeftBrace) {
74 | // \u{N}
75 | let digit = toHex(source.charCodeAt(++parser.index));
76 |
77 | if (digit < 0) return Escape.InvalidHex;
78 |
79 | while (digit >= 0) {
80 | code = (code << 4) | digit;
81 |
82 | if (code > Chars.LastUnicodeChar) return Escape.OutOfRange;
83 |
84 | ch = source.charCodeAt(++parser.index);
85 |
86 | digit = toHex(ch);
87 | }
88 | // At least 4 characters have to be scanned
89 | if (code < 0 || ch !== Chars.RightBrace) return Escape.InvalidHex;
90 |
91 | parser.index++; // consumes '}'
92 |
93 | return code;
94 | }
95 |
96 | // \uNNNN
97 |
98 | let i = 0;
99 | let digit: number | null = null;
100 | for (i = 0; i < 4; i++) {
101 | digit = toHex(source.charCodeAt(parser.index++));
102 | if (digit < 0) return Escape.InvalidHex;
103 | code = (code << 4) | digit;
104 | }
105 |
106 | return code;
107 | }
108 |
109 | case Chars.LowerX: {
110 | const hi = toHex(ch);
111 | if (hi < 0) return Escape.InvalidHex;
112 | const lo = toHex(source.charCodeAt(++parser.index));
113 | if (lo < 0) return Escape.InvalidHex;
114 | parser.index++;
115 | return (hi << 4) | lo;
116 | }
117 |
118 | // Null character, octals
119 | case Chars.Zero:
120 | case Chars.One:
121 | case Chars.Two:
122 | case Chars.Three:
123 | case Chars.Four:
124 | case Chars.Five:
125 | case Chars.Six:
126 | case Chars.Seven: {
127 | const code = first - Chars.Zero;
128 |
129 | if ((context & 0b00000000000000000000010000010000) > 0) {
130 | // Verify that it's `\0` if we're in strict mode.
131 | if (first === Chars.Zero && (ch < Chars.Zero || ch > Chars.Nine)) return code;
132 | return Escape.StrictOctal;
133 | }
134 |
135 | if (ch >= Chars.Zero && ch <= Chars.Seven) {
136 | parser.flags |= Flags.Octals;
137 |
138 | let index = parser.index;
139 |
140 | const value = source.charCodeAt(index) - Chars.Zero;
141 |
142 | if (first >= Chars.Zero && first <= Chars.Three) {
143 | const ch1 = source.charCodeAt(index + 1);
144 |
145 | if (ch1 >= Chars.Zero && ch1 <= Chars.Seven) {
146 | parser.index = index += 2;
147 | return code * 64 + value * 8 + ch1 - Chars.Zero;
148 | }
149 | }
150 | parser.index = index + 1;
151 |
152 | return code * 8 + value;
153 | } else if (code !== 0) {
154 | parser.flags |= Flags.Octals;
155 | }
156 |
157 | return code;
158 | }
159 | case Chars.Eight:
160 | case Chars.Nine:
161 | return Escape.EightOrNine;
162 | case Chars.CarriageReturn:
163 | const index = parser.index;
164 | if (index < parser.length) {
165 | if (source.charCodeAt(index) === Chars.LineFeed) {
166 | parser.index = index + 1;
167 | }
168 | }
169 |
170 | // falls through
171 |
172 | case Chars.LineFeed:
173 | parser.offset = parser.index;
174 | parser.curLine++;
175 | return Escape.Empty;
176 | default:
177 | return first;
178 | }
179 | }
180 |
181 | export function handleStringError(parser: ParserState, code: Escape, isTemplate: 0 | 1): void {
182 | switch (code) {
183 | case Escape.Empty:
184 | return;
185 |
186 | case Escape.StrictOctal:
187 | report(parser, isTemplate ? Errors.TemplateOctalLiteral : Errors.StrictOctalEscape);
188 |
189 | case Escape.EightOrNine:
190 | report(parser, Errors.InvalidEightAndNine);
191 |
192 | case Escape.InvalidHex:
193 | report(parser, Errors.InvalidHexEscapeSequence);
194 |
195 | case Escape.OutOfRange:
196 | report(parser, Errors.UnicodeOverflow);
197 |
198 | default:
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/test/parser/expressions/logicalAssignment.ts:
--------------------------------------------------------------------------------
1 | import { pass } from '../core';
2 | import { Context } from '../../../src/parser/common';
3 |
4 | pass('Expressions - Logical Assignment (pass)', [
5 | [
6 | `x &&= 2`,
7 | Context.OptionsNext | Context.OptionsLoc,
8 | {
9 | type: 'Program',
10 | sourceType: 'script',
11 | body: [
12 | {
13 | type: 'ExpressionStatement',
14 | expression: {
15 | type: 'AssignmentExpression',
16 | left: {
17 | type: 'Identifier',
18 | name: 'x',
19 | start: 0,
20 | end: 1,
21 | loc: {
22 | start: {
23 | line: 1,
24 | column: 0
25 | },
26 | end: {
27 | line: 1,
28 | column: 1
29 | }
30 | }
31 | },
32 | operator: '&&=',
33 | right: {
34 | type: 'Literal',
35 | value: 2,
36 | start: 6,
37 | end: 7,
38 | loc: {
39 | start: {
40 | line: 1,
41 | column: 6
42 | },
43 | end: {
44 | line: 1,
45 | column: 7
46 | }
47 | }
48 | },
49 | start: 0,
50 | end: 7,
51 | loc: {
52 | start: {
53 | line: 1,
54 | column: 0
55 | },
56 | end: {
57 | line: 1,
58 | column: 7
59 | }
60 | }
61 | },
62 | start: 0,
63 | end: 7,
64 | loc: {
65 | start: {
66 | line: 1,
67 | column: 0
68 | },
69 | end: {
70 | line: 1,
71 | column: 7
72 | }
73 | }
74 | }
75 | ],
76 | start: 0,
77 | end: 7,
78 | loc: {
79 | start: {
80 | line: 1,
81 | column: 0
82 | },
83 | end: {
84 | line: 1,
85 | column: 7
86 | }
87 | }
88 | }
89 | ],
90 | [
91 | `x ??= 1`,
92 | Context.OptionsNext | Context.OptionsLoc,
93 | {
94 | type: 'Program',
95 | sourceType: 'script',
96 | body: [
97 | {
98 | type: 'ExpressionStatement',
99 | expression: {
100 | type: 'AssignmentExpression',
101 | left: {
102 | type: 'Identifier',
103 | name: 'x',
104 | start: 0,
105 | end: 1,
106 | loc: {
107 | start: {
108 | line: 1,
109 | column: 0
110 | },
111 | end: {
112 | line: 1,
113 | column: 1
114 | }
115 | }
116 | },
117 | operator: '??=',
118 | right: {
119 | type: 'Literal',
120 | value: 1,
121 | start: 6,
122 | end: 7,
123 | loc: {
124 | start: {
125 | line: 1,
126 | column: 6
127 | },
128 | end: {
129 | line: 1,
130 | column: 7
131 | }
132 | }
133 | },
134 | start: 0,
135 | end: 7,
136 | loc: {
137 | start: {
138 | line: 1,
139 | column: 0
140 | },
141 | end: {
142 | line: 1,
143 | column: 7
144 | }
145 | }
146 | },
147 | start: 0,
148 | end: 7,
149 | loc: {
150 | start: {
151 | line: 1,
152 | column: 0
153 | },
154 | end: {
155 | line: 1,
156 | column: 7
157 | }
158 | }
159 | }
160 | ],
161 | start: 0,
162 | end: 7,
163 | loc: {
164 | start: {
165 | line: 1,
166 | column: 0
167 | },
168 | end: {
169 | line: 1,
170 | column: 7
171 | }
172 | }
173 | }
174 | ],
175 | [
176 | `x ||= 1`,
177 | Context.OptionsNext | Context.OptionsLoc,
178 | {
179 | type: 'Program',
180 | sourceType: 'script',
181 | body: [
182 | {
183 | type: 'ExpressionStatement',
184 | expression: {
185 | type: 'AssignmentExpression',
186 | left: {
187 | type: 'Identifier',
188 | name: 'x',
189 | start: 0,
190 | end: 1,
191 | loc: {
192 | start: {
193 | line: 1,
194 | column: 0
195 | },
196 | end: {
197 | line: 1,
198 | column: 1
199 | }
200 | }
201 | },
202 | operator: '||=',
203 | right: {
204 | type: 'Literal',
205 | value: 1,
206 | start: 6,
207 | end: 7,
208 | loc: {
209 | start: {
210 | line: 1,
211 | column: 6
212 | },
213 | end: {
214 | line: 1,
215 | column: 7
216 | }
217 | }
218 | },
219 | start: 0,
220 | end: 7,
221 | loc: {
222 | start: {
223 | line: 1,
224 | column: 0
225 | },
226 | end: {
227 | line: 1,
228 | column: 7
229 | }
230 | }
231 | },
232 | start: 0,
233 | end: 7,
234 | loc: {
235 | start: {
236 | line: 1,
237 | column: 0
238 | },
239 | end: {
240 | line: 1,
241 | column: 7
242 | }
243 | }
244 | }
245 | ],
246 | start: 0,
247 | end: 7,
248 | loc: {
249 | start: {
250 | line: 1,
251 | column: 0
252 | },
253 | end: {
254 | line: 1,
255 | column: 7
256 | }
257 | }
258 | }
259 | ]
260 | ]);
261 |
--------------------------------------------------------------------------------
/test/parser/miscellaneous/trailing.ts:
--------------------------------------------------------------------------------
1 | import { fail } from '../core';
2 | import { Context } from '../../../src/parser/common';
3 | import { parseRoot } from '../../../src/seafox';
4 | import * as t from 'assert';
5 |
6 | fail('Miscellaneous - Trailing comma (fail)', [
7 | [' function a(b,,) {}', Context.Empty],
8 | [' function* a(b,,) {}', Context.Empty],
9 | ['(function a(b,,) {});', Context.Empty],
10 | ['(function* a(b,,) {});', Context.Empty],
11 | ['(function (b,,) {});', Context.Empty],
12 | ['(function* (b,,) {});', Context.Empty],
13 | [' function a(b,c,d,,) {}', Context.Empty],
14 | [' function* a(b,c,d,,) {}', Context.Empty],
15 | ['(function a(b,c,d,,) {});', Context.Empty],
16 | ['(function* a(b,c,d,,) {});', Context.Empty],
17 | ['(function (b,c,d,,) {});', Context.Empty],
18 | ['(function* (b,c,d,,) {});', Context.Empty],
19 | ['(function* (b,c,d,,) {});', Context.OptionsDisableWebCompat],
20 | ['(b,,) => {};', Context.Empty],
21 | ['(b,c,d,,) => {};', Context.Empty],
22 | ['a(1,,);', Context.Empty],
23 | ['a(1,2,3,,);', Context.Empty],
24 | [' function a1(,) {}', Context.Empty],
25 | [' function* a2(,) {}', Context.Empty],
26 | ['(function a3(,) {});', Context.Empty],
27 | ['(function* a4(,) {});', Context.Empty],
28 | ['(function (,) {});', Context.Empty],
29 | ['(function* (,) {});', Context.Empty],
30 | ['(function (,) {});', Context.OptionsDisableWebCompat],
31 | ['(function* (,) {});', Context.OptionsDisableWebCompat],
32 | ['(,) => {};', Context.Empty],
33 | ['a1(,);', Context.Empty],
34 | [' function a(...b,) {}', Context.Empty],
35 | [' function* a(...b,) {}', Context.Empty],
36 | ['(function a(...b,) {});', Context.Empty],
37 | ['(function* a(...b,) {});', Context.Empty],
38 | ['(function (...b,) {});', Context.Empty],
39 | ['(function* (...b,) {});', Context.Empty],
40 | [' function a(b, c, ...d,) {}', Context.Empty],
41 | [' function* a(b, c, ...d,) {}', Context.Empty],
42 | ['(function a(b, c, ...d,) {});', Context.Empty],
43 | ['(function* a(b, c, ...d,) {});', Context.Empty],
44 | ['(function (b, c, ...d,) {});', Context.Empty],
45 | ['(...b,) => {};', Context.Empty],
46 | ['(b, c, ...d,) => {};', Context.Empty],
47 | ['(,);', Context.Empty],
48 | ['(a,);', Context.Empty],
49 | ['(a,b,c,);', Context.Empty],
50 | ['foo (,) => 0', Context.Empty],
51 | [', => 0', Context.Empty],
52 | [', () => 0', Context.Empty],
53 | ['async (,) => 0', Context.Empty],
54 | ['(function* (b, c, ...d,) {});', Context.Empty],
55 | ['class A {foo(,) {}}', Context.Strict | Context.Module],
56 | ['class A {static foo(,) {}}', Context.Empty],
57 | ['(class {static foo(,) {}})', Context.Empty],
58 | ['(...b,) => {};', Context.Empty],
59 | ['(b, c, ...d,) => {};', Context.Empty],
60 | ['n, op, val,', Context.Empty],
61 | ['foo(a,,) => 0', Context.Empty],
62 | ['async (a,,) => 0', Context.Empty],
63 | ['(b,,) => {};', Context.Empty],
64 | ['(b,c,d,,) => {};', Context.Empty],
65 | ['a(1,,);', Context.Empty],
66 | ['a(1,2,3,,);', Context.Empty],
67 | [' function a1(,) {}', Context.Empty],
68 | ['a(1,,);', Context.Strict | Context.Module],
69 | ['a(1,2,3,,);', Context.Strict | Context.Module],
70 | [' function a1(,) {}', Context.Strict | Context.Module],
71 | [' function* a2(,) {}', Context.Empty],
72 | ['(function a3(,) {});', Context.Empty],
73 | ['(function* a4(,) {});', Context.Empty],
74 | ['(function (,) {});', Context.Empty],
75 | ['(function* (,) {});', Context.Empty],
76 | ['(,) => {};', Context.Empty],
77 | ['a1(,);', Context.Empty],
78 | [' function a(...b,) {}', Context.Empty],
79 | [' function a(...b,) {}', Context.Strict | Context.Module],
80 | [' function* a(...b,) {}', Context.Empty],
81 | ['(function a(...b,) {});', Context.Empty],
82 | ['(function* a(...b,) {});', Context.Empty],
83 | ['(function (...b,) {});', Context.Empty],
84 | ['(function* (...b,) {});', Context.Empty],
85 | ['(function (...b,) {});', Context.Strict | Context.Module],
86 | ['(function* (...b,) {});', Context.Strict | Context.Module]
87 | ]);
88 |
89 | // Comma is not permitted after the rest element
90 | const invalidRest = [
91 | 'function foo(...a,) { }',
92 | '(function(...a,) { })',
93 | '(...a,) => a',
94 | 'async (...a,) => a',
95 | '({foo(...a,) {}})',
96 | 'class A {foo(...a,) {}}',
97 | 'class A {static foo(...a,) {}}',
98 | '(class {foo(...a,) {}})',
99 | '(class {static foo(...a,) {}})'
100 | ];
101 |
102 | for (const arg of invalidRest) {
103 | it(`"use strict"; ${arg}`, () => {
104 | t.throws(() => {
105 | parseRoot(`"use strict"; ${arg}`, Context.Empty);
106 | });
107 | });
108 | it(`${arg}`, () => {
109 | t.throws(() => {
110 | parseRoot(`${arg}`, Context.Strict | Context.Module);
111 | });
112 | });
113 | }
114 |
115 | for (const arg of [
116 | ' function a(b,) {}',
117 | ' function* a(b,) {}',
118 | '(function a(b,) {});',
119 | '(function* a(b,) {});',
120 | '(function (b,) {});',
121 | '(function* (b,) {});',
122 | ' function a(b,c,d,) {}',
123 | ' function* a(b,c,d,) {}',
124 | '(function a(b,c,d,) {});',
125 | '(function* a(b,c,d,) {});',
126 | '(function (b,c,d,) {});',
127 | '(function* (b,c,d,) {});',
128 | '(b,) => {};',
129 | '(b,c,d,) => {};',
130 | 'a(1,);',
131 | 'a(1,2,3,);',
132 | 'a(...[],);',
133 | 'a(1, 2, ...[],);',
134 | 'a(...[], 2, ...[],);',
135 | 'a, b, (c, d) => 0',
136 | '(a, b, (c, d) => 0)',
137 | '(a, b) => 0, (c, d) => 1',
138 | '(a, b => {}, a => a + 1)',
139 | '((a, b) => {}, (a => a + 1))',
140 | '(a, (a, (b, c) => 0))',
141 | 'async (a, (a, (b, c) => 0))',
142 | '[...a,]',
143 | '[...a, ,]',
144 | '[, ...a]',
145 | '[...[...a]]',
146 | '[, ...a]',
147 | '[, , ...a]',
148 | 'function a(b,) {}',
149 | 'function* a(b,) {}',
150 | '(function a(b,) {});',
151 | '(function* a(b,) {});',
152 | '(function (b,) {});',
153 | '(function* (b,) {});',
154 | ' function a(b,c,d,) {}',
155 | ' function* a(b,c,d,) {}',
156 | '(function a(b,c,d,) {});',
157 | '(function* a(b,c,d,) {});',
158 | '(function (b,c,d,) {});',
159 | '(function* (b,c,d,) {});',
160 | 'class Foo { bar(a,) { } }',
161 | '(1, y)',
162 | '0, f(n - 1);',
163 | '(b,) => {};',
164 | '(b,c,d,) => {};',
165 | 'a(1,);',
166 | 'a(1,2,3,);',
167 | 'a(...[],);',
168 | 'a(1, 2, ...[],);',
169 | 'a(...[], 2, ...[],);',
170 | 'a, b => 0'
171 | ]) {
172 | it(`"use strict"; ${arg}`, () => {
173 | t.doesNotThrow(() => {
174 | parseRoot(`"use strict"; ${arg}`, Context.Empty);
175 | });
176 | });
177 |
178 | it(`${arg}`, () => {
179 | t.doesNotThrow(() => {
180 | parseRoot(`${arg}`, Context.Strict | Context.Module);
181 | });
182 | });
183 | }
184 |
--------------------------------------------------------------------------------
/test/scanner/punctuators.ts:
--------------------------------------------------------------------------------
1 | import * as t from 'assert';
2 | import { Context } from '../../src/parser/common';
3 | import { create } from '../../src/parser/core';
4 | import { Token } from '../../src/token';
5 | import { scan } from '../../src/scanner/scan';
6 |
7 | describe('Scanner - Punctuator', () => {
8 | describe('scan()', () => {
9 | interface Opts {
10 | source: string;
11 | context: Context;
12 | token: Token;
13 | hasNext: boolean;
14 | line: number;
15 | column: number;
16 | }
17 |
18 | function pass(name: string, opts: Opts) {
19 | it(name, () => {
20 | const parser = create(opts.source);
21 |
22 | t.deepEqual(
23 | {
24 | token: scan(
25 | parser,
26 | opts.context,
27 | opts.source,
28 | 1,
29 | opts.source.length,
30 | Token.EOF,
31 | 0,
32 | true,
33 | /* allowRegExp */ 0
34 | ),
35 | hasNext: parser.index < parser.source.length,
36 | line: parser.curLine,
37 | column: parser.index - parser.offset
38 | },
39 | {
40 | token: opts.token,
41 | hasNext: opts.hasNext,
42 | line: opts.line,
43 | column: opts.column
44 | }
45 | );
46 | });
47 | }
48 |
49 | pass('scans end of source', {
50 | source: '',
51 | context: Context.Empty,
52 | token: Token.EOF,
53 | hasNext: false,
54 | line: 1,
55 | column: 0
56 | });
57 |
58 | const tokens: Array<[Context, Token, string]> = [
59 | /* Punctuators */
60 | [Context.Empty, Token.Arrow, '=>'],
61 | [Context.Empty, Token.LeftParen, '('],
62 | [Context.Empty, Token.LeftBrace, '{'],
63 | [Context.Empty, Token.Period, '.'],
64 | [Context.Empty, Token.Ellipsis, '...'],
65 | [Context.Empty, Token.RightBrace, '}'],
66 | [Context.Empty, Token.RightParen, ')'],
67 | [Context.Empty, Token.Semicolon, ';'],
68 | [Context.Empty, Token.Comma, ','],
69 | [Context.Empty, Token.LeftBracket, '['],
70 | [Context.Empty, Token.RightBracket, ']'],
71 | [Context.Empty, Token.Colon, ':'],
72 | [Context.Empty, Token.QuestionMark, '?'],
73 |
74 | /* Update operators */
75 | [Context.Empty, Token.Increment, '++'],
76 | [Context.Empty, Token.Decrement, '--'],
77 |
78 | /* Assign operators */
79 | [Context.Empty, Token.Assign, '='],
80 | [Context.Empty, Token.LogicalOrAssign, '||='],
81 | [Context.Empty, Token.LogicalAndAssign, '&&='],
82 | [Context.Empty, Token.CoalesceAssign, '??='],
83 | [Context.Empty, Token.ShiftLeftAssign, '<<='],
84 | [Context.Empty, Token.ShiftRightAssign, '>>='],
85 | [Context.Empty, Token.LogicalShiftRightAssign, '>>>='],
86 | [Context.Empty, Token.ExponentiateAssign, '**='],
87 | [Context.Empty, Token.AddAssign, '+='],
88 | [Context.Empty, Token.SubtractAssign, '-='],
89 | [Context.Empty, Token.MultiplyAssign, '*='],
90 | [Context.Empty, Token.DivideAssign, '/='],
91 | [Context.Empty, Token.ModuloAssign, '%='],
92 | [Context.Empty, Token.BitwiseXorAssign, '^='],
93 | [Context.Empty, Token.BitwiseOrAssign, '|='],
94 | [Context.Empty, Token.BitwiseAndAssign, '&='],
95 |
96 | /* Unary/binary operators */
97 | [Context.Empty, Token.Negate, '!'],
98 | [Context.Empty, Token.Complement, '~'],
99 | [Context.Empty, Token.Add, '+'],
100 | [Context.Empty, Token.Subtract, '-'],
101 | [Context.Empty, Token.Multiply, '*'],
102 | [Context.Empty, Token.Modulo, '%'],
103 | [Context.Empty, Token.Divide, '/'],
104 | [Context.Empty, Token.Exponentiate, '**'],
105 | [Context.Empty, Token.LogicalAnd, '&&'],
106 | [Context.Empty, Token.LogicalOr, '||'],
107 | [Context.Empty, Token.StrictEqual, '==='],
108 | [Context.Empty, Token.StrictNotEqual, '!=='],
109 | [Context.Empty, Token.LooseEqual, '=='],
110 | [Context.Empty, Token.LooseNotEqual, '!='],
111 | [Context.Empty, Token.LessThanOrEqual, '<='],
112 | [Context.Empty, Token.GreaterThanOrEqual, '>='],
113 | [Context.Empty, Token.LessThan, '<'],
114 | [Context.Empty, Token.GreaterThan, '>'],
115 | [Context.Empty, Token.ShiftLeft, '<<'],
116 | [Context.Empty, Token.ShiftRight, '>>'],
117 | [Context.Empty, Token.LogicalShiftRight, '>>>'],
118 | [Context.Empty, Token.BitwiseAnd, '&'],
119 | [Context.Empty, Token.BitwiseOr, '|'],
120 | [Context.Empty, Token.BitwiseXor, '^'],
121 | [Context.OptionsNext, Token.QuestionMarkPeriod, '?.'],
122 | [Context.OptionsNext, Token.Coalesce, '??']
123 | ];
124 |
125 | for (const [ctx, token, op] of tokens) {
126 | it(`scans '${op}' at the end`, () => {
127 | const parser = create(op);
128 | const found = scan(parser, ctx, op, 1, op.length, Token.EOF, 0, true, /* allowRegExp */ 0);
129 | t.deepEqual(
130 | {
131 | token: found,
132 | hasNext: parser.index < parser.source.length,
133 | line: parser.curLine,
134 | column: parser.index - parser.offset
135 | },
136 | {
137 | token: token,
138 | hasNext: false,
139 | line: 1,
140 | column: op.length
141 | }
142 | );
143 | });
144 |
145 | it(`scans '${op}' with more to go`, () => {
146 | const parser = create(`${op} rest`);
147 | const found = scan(parser, ctx, op, 1, op.length, Token.EOF, 0, true, /* allowRegExp */ 0);
148 |
149 | t.deepEqual(
150 | {
151 | token: found,
152 | hasNext: parser.index < parser.source.length,
153 | line: parser.curLine,
154 | column: parser.index - parser.offset
155 | },
156 | {
157 | token,
158 | hasNext: true,
159 | line: 1,
160 | column: op.length
161 | }
162 | );
163 | });
164 | }
165 |
166 | it("scans '.' in '..'", () => {
167 | const parser = create('..');
168 | const found = scan(parser, Context.Empty, '..', 1, 2, Token.EOF, 0, true, /* allowRegExp */ 0);
169 |
170 | t.deepEqual(
171 | {
172 | token: found,
173 | hasNext: parser.index < parser.source.length,
174 | line: parser.curLine,
175 | column: parser.index - parser.offset
176 | },
177 | {
178 | token: Token.Period,
179 | hasNext: true,
180 | line: 1,
181 | column: 1
182 | }
183 | );
184 | });
185 | });
186 | });
187 |
--------------------------------------------------------------------------------
/test/parser/expressions/regexp.ts:
--------------------------------------------------------------------------------
1 | import { pass } from '../core';
2 | import { Context } from '../../../src/parser/common';
3 |
4 | pass('Expressions - Regular expression', [
5 | [
6 | `[a, /a/]`,
7 | Context.OptionsNext | Context.OptionsLoc,
8 | {
9 | type: 'Program',
10 | sourceType: 'script',
11 | body: [
12 | {
13 | type: 'ExpressionStatement',
14 | expression: {
15 | type: 'ArrayExpression',
16 | elements: [
17 | {
18 | type: 'Identifier',
19 | name: 'a',
20 | start: 1,
21 | end: 2,
22 | loc: {
23 | start: {
24 | line: 1,
25 | column: 1
26 | },
27 | end: {
28 | line: 1,
29 | column: 2
30 | }
31 | }
32 | },
33 | {
34 | type: 'Literal',
35 | value: /a/,
36 | regex: {
37 | pattern: 'a',
38 | flags: ''
39 | },
40 | start: 4,
41 | end: 7,
42 | loc: {
43 | start: {
44 | line: 1,
45 | column: 4
46 | },
47 | end: {
48 | line: 1,
49 | column: 7
50 | }
51 | }
52 | }
53 | ],
54 | start: 0,
55 | end: 8,
56 | loc: {
57 | start: {
58 | line: 1,
59 | column: 0
60 | },
61 | end: {
62 | line: 1,
63 | column: 8
64 | }
65 | }
66 | },
67 | start: 0,
68 | end: 8,
69 | loc: {
70 | start: {
71 | line: 1,
72 | column: 0
73 | },
74 | end: {
75 | line: 1,
76 | column: 8
77 | }
78 | }
79 | }
80 | ],
81 | start: 0,
82 | end: 8,
83 | loc: {
84 | start: {
85 | line: 1,
86 | column: 0
87 | },
88 | end: {
89 | line: 1,
90 | column: 8
91 | }
92 | }
93 | }
94 | ],
95 | [
96 | `var a = /aaa/;`,
97 | Context.OptionsNext | Context.OptionsLoc,
98 | {
99 | type: 'Program',
100 | sourceType: 'script',
101 | body: [
102 | {
103 | type: 'VariableDeclaration',
104 | kind: 'var',
105 | declarations: [
106 | {
107 | type: 'VariableDeclarator',
108 | init: {
109 | type: 'Literal',
110 | value: /aaa/,
111 | regex: {
112 | pattern: 'aaa',
113 | flags: ''
114 | },
115 | start: 8,
116 | end: 13,
117 | loc: {
118 | start: {
119 | line: 1,
120 | column: 8
121 | },
122 | end: {
123 | line: 1,
124 | column: 13
125 | }
126 | }
127 | },
128 | id: {
129 | type: 'Identifier',
130 | name: 'a',
131 | start: 4,
132 | end: 5,
133 | loc: {
134 | start: {
135 | line: 1,
136 | column: 4
137 | },
138 | end: {
139 | line: 1,
140 | column: 5
141 | }
142 | }
143 | },
144 | start: 4,
145 | end: 13,
146 | loc: {
147 | start: {
148 | line: 1,
149 | column: 4
150 | },
151 | end: {
152 | line: 1,
153 | column: 13
154 | }
155 | }
156 | }
157 | ],
158 | start: 0,
159 | end: 14,
160 | loc: {
161 | start: {
162 | line: 1,
163 | column: 0
164 | },
165 | end: {
166 | line: 1,
167 | column: 14
168 | }
169 | }
170 | }
171 | ],
172 | start: 0,
173 | end: 14,
174 | loc: {
175 | start: {
176 | line: 1,
177 | column: 0
178 | },
179 | end: {
180 | line: 1,
181 | column: 14
182 | }
183 | }
184 | }
185 | ]
186 | /* [
187 | `/aaa/ / /aaa/;`,
188 | Context.OptionsNext | Context.OptionsLoc | Context.OptionsRaw,
189 | {
190 | "type": "Program",
191 | "sourceType": "script",
192 | "body": [
193 | {
194 | "type": "ExpressionStatement",
195 | "expression": {
196 | "type": "BinaryExpression",
197 | "left": {
198 | "type": "Literal",
199 | "value": {},
200 | "regex": {
201 | "pattern": "aaa",
202 | "flags": ""
203 | },
204 | "start": 0,
205 | "end": 5,
206 | "loc": {
207 | "start": {
208 | "line": 1,
209 | "column": 0
210 | },
211 | "end": {
212 | "line": 1,
213 | "column": 5
214 | }
215 | }
216 | },
217 | "right": {
218 | "type": "Literal",
219 | "value": {},
220 | "regex": {
221 | "pattern": "aaa",
222 | "flags": ""
223 | },
224 | "start": 8,
225 | "end": 13,
226 | "loc": {
227 | "start": {
228 | "line": 1,
229 | "column": 8
230 | },
231 | "end": {
232 | "line": 1,
233 | "column": 13
234 | }
235 | }
236 | },
237 | "operator": "/",
238 | "start": 0,
239 | "end": 13,
240 | "loc": {
241 | "start": {
242 | "line": 1,
243 | "column": 0
244 | },
245 | "end": {
246 | "line": 1,
247 | "column": 13
248 | }
249 | }
250 | },
251 | "start": 0,
252 | "end": 14,
253 | "loc": {
254 | "start": {
255 | "line": 1,
256 | "column": 0
257 | },
258 | "end": {
259 | "line": 1,
260 | "column": 14
261 | }
262 | }
263 | }
264 | ],
265 | "start": 0,
266 | "end": 14,
267 | "loc": {
268 | "start": {
269 | "line": 1,
270 | "column": 0
271 | },
272 | "end": {
273 | "line": 1,
274 | "column": 14
275 | }
276 | }
277 | }
278 | ]*/
279 | ]);
280 |
--------------------------------------------------------------------------------
/test/parser/statements/with.ts:
--------------------------------------------------------------------------------
1 | import { pass, fail } from '../core';
2 | import { Context } from '../../../src/parser/common';
3 |
4 | fail('Statements - With (fail)', [
5 | ['with(1) b: function a(){}', Context.OptionsDisableWebCompat],
6 | ['with ({}) async function f() {}', Context.OptionsDisableWebCompat],
7 | ['with ({}) function f() {}', Context.OptionsDisableWebCompat],
8 | ['with ({}) let x;', Context.Empty],
9 | ['with ({}) { }', Context.Strict],
10 | [`with (x) foo;`, Context.Strict],
11 | [`with ({}) let [a] = [42];`, Context.Empty],
12 | [`with ({}) let [a]`, Context.Empty],
13 | [`with ({}) let 1`, Context.Empty],
14 | [`with ({}) let []`, Context.Empty],
15 | [`while(true) let[a] = 0`, Context.Empty]
16 | ]);
17 |
18 | pass('Statements - With (pass)', [
19 | [
20 | `with ({}) let`,
21 | Context.OptionsLoc,
22 | {
23 | type: 'Program',
24 | sourceType: 'script',
25 | body: [
26 | {
27 | type: 'WithStatement',
28 | object: {
29 | type: 'ObjectExpression',
30 | properties: [],
31 | start: 6,
32 | end: 8,
33 | loc: {
34 | start: {
35 | line: 1,
36 | column: 6
37 | },
38 | end: {
39 | line: 1,
40 | column: 8
41 | }
42 | }
43 | },
44 | body: {
45 | type: 'ExpressionStatement',
46 | expression: {
47 | type: 'Identifier',
48 | name: 'let',
49 | start: 10,
50 | end: 13,
51 | loc: {
52 | start: {
53 | line: 1,
54 | column: 10
55 | },
56 | end: {
57 | line: 1,
58 | column: 13
59 | }
60 | }
61 | },
62 | start: 10,
63 | end: 13,
64 | loc: {
65 | start: {
66 | line: 1,
67 | column: 10
68 | },
69 | end: {
70 | line: 1,
71 | column: 13
72 | }
73 | }
74 | },
75 | start: 0,
76 | end: 13,
77 | loc: {
78 | start: {
79 | line: 1,
80 | column: 0
81 | },
82 | end: {
83 | line: 1,
84 | column: 13
85 | }
86 | }
87 | }
88 | ],
89 | start: 0,
90 | end: 13,
91 | loc: {
92 | start: {
93 | line: 1,
94 | column: 0
95 | },
96 | end: {
97 | line: 1,
98 | column: 13
99 | }
100 | }
101 | }
102 | ],
103 | [
104 | `with (x) { foo }`,
105 | Context.OptionsLoc,
106 | {
107 | type: 'Program',
108 | sourceType: 'script',
109 | body: [
110 | {
111 | type: 'WithStatement',
112 | object: {
113 | type: 'Identifier',
114 | name: 'x',
115 | start: 6,
116 | end: 7,
117 | loc: {
118 | start: {
119 | line: 1,
120 | column: 6
121 | },
122 | end: {
123 | line: 1,
124 | column: 7
125 | }
126 | }
127 | },
128 | body: {
129 | type: 'BlockStatement',
130 | body: [
131 | {
132 | type: 'ExpressionStatement',
133 | expression: {
134 | type: 'Identifier',
135 | name: 'foo',
136 | start: 11,
137 | end: 14,
138 | loc: {
139 | start: {
140 | line: 1,
141 | column: 11
142 | },
143 | end: {
144 | line: 1,
145 | column: 14
146 | }
147 | }
148 | },
149 | start: 11,
150 | end: 14,
151 | loc: {
152 | start: {
153 | line: 1,
154 | column: 11
155 | },
156 | end: {
157 | line: 1,
158 | column: 14
159 | }
160 | }
161 | }
162 | ],
163 | start: 9,
164 | end: 16,
165 | loc: {
166 | start: {
167 | line: 1,
168 | column: 9
169 | },
170 | end: {
171 | line: 1,
172 | column: 16
173 | }
174 | }
175 | },
176 | start: 0,
177 | end: 16,
178 | loc: {
179 | start: {
180 | line: 1,
181 | column: 0
182 | },
183 | end: {
184 | line: 1,
185 | column: 16
186 | }
187 | }
188 | }
189 | ],
190 | start: 0,
191 | end: 16,
192 | loc: {
193 | start: {
194 | line: 1,
195 | column: 0
196 | },
197 | end: {
198 | line: 1,
199 | column: 16
200 | }
201 | }
202 | }
203 | ],
204 | [
205 | `with (x) foo;`,
206 | Context.OptionsLoc,
207 | {
208 | type: 'Program',
209 | sourceType: 'script',
210 | body: [
211 | {
212 | type: 'WithStatement',
213 | object: {
214 | type: 'Identifier',
215 | name: 'x',
216 | start: 6,
217 | end: 7,
218 | loc: {
219 | start: {
220 | line: 1,
221 | column: 6
222 | },
223 | end: {
224 | line: 1,
225 | column: 7
226 | }
227 | }
228 | },
229 | body: {
230 | type: 'ExpressionStatement',
231 | expression: {
232 | type: 'Identifier',
233 | name: 'foo',
234 | start: 9,
235 | end: 12,
236 | loc: {
237 | start: {
238 | line: 1,
239 | column: 9
240 | },
241 | end: {
242 | line: 1,
243 | column: 12
244 | }
245 | }
246 | },
247 | start: 9,
248 | end: 13,
249 | loc: {
250 | start: {
251 | line: 1,
252 | column: 9
253 | },
254 | end: {
255 | line: 1,
256 | column: 13
257 | }
258 | }
259 | },
260 | start: 0,
261 | end: 13,
262 | loc: {
263 | start: {
264 | line: 1,
265 | column: 0
266 | },
267 | end: {
268 | line: 1,
269 | column: 13
270 | }
271 | }
272 | }
273 | ],
274 | start: 0,
275 | end: 13,
276 | loc: {
277 | start: {
278 | line: 1,
279 | column: 0
280 | },
281 | end: {
282 | line: 1,
283 | column: 13
284 | }
285 | }
286 | }
287 | ]
288 | ]);
289 |
--------------------------------------------------------------------------------
/test/parser/miscellaneous/early-errors.ts:
--------------------------------------------------------------------------------
1 | import { fail } from '../core';
2 | import { Context } from '../../../src/parser/common';
3 |
4 | fail('Miscellaneous - Early errors', [
5 | ['{ a = 0 });', Context.Empty],
6 | ['(...a)', Context.Empty],
7 | [`(a, ...b)`, Context.Empty],
8 | ['(((a, ...b)))', Context.Empty],
9 | ['0++', Context.Empty],
10 | ['({a: 0} = 0);', Context.Empty],
11 | ['({a(b){}} = 0)', Context.Empty],
12 | ['"use strict"; ({arguments = 1} = 2);', Context.Empty],
13 | [`[0] = 0;`, Context.Empty],
14 | [`0 = 0;`, Context.Empty],
15 | [`({a}) = 0;`, Context.Empty],
16 | [`([a]) = 0;`, Context.Empty],
17 | [`({a} += 0);`, Context.Empty],
18 | [`[a] *= 0;`, Context.Empty],
19 | [`0 /= 0;`, Context.Empty],
20 | [`[...{a: 0}] = 0;`, Context.Empty],
21 | [`[...[0]] = 0;`, Context.Empty],
22 | [`[...0] = 0;`, Context.Empty],
23 | ['a\\u{0}', Context.Empty],
24 | ['a\\u0000', Context.Empty],
25 | ['\\u{0}', Context.Empty],
26 | ['[...new a] = 0;', Context.Empty],
27 | ['for(([0]) of 0);', Context.Empty],
28 | ['for((0) of 0);', Context.Empty],
29 | [`/./\\u{69}`, Context.Empty],
30 | ["'use strict'; +implements;", Context.Empty],
31 | ["'use strict'; let:0;", Context.Empty],
32 | ["'use strict'; +yield;", Context.Empty],
33 | ["!{ get a() { 'use strict'; +let; } }", Context.Empty],
34 | ['class let {}', Context.Empty],
35 | ['class l\\u{65}t {}', Context.Empty],
36 | ['(class yield {})', Context.Empty],
37 | ['({ a(){ super(); } });', Context.Empty],
38 | ['"use strict"; ([yield] = a)', Context.Empty],
39 | ['class a extends b { constructor() { !{*constructor() { super(); }}; } }', Context.Empty],
40 | ['"use strict"; !function* arguments(){}', Context.Empty],
41 | ["'use strict'; delete ((a));", Context.Empty],
42 | ['!function(a){ super.b }', Context.Empty],
43 | [`async function a(b = await (0)) {}`, Context.Empty],
44 | ['(async function(b = await (0)) {})', Context.Empty],
45 | ['({ async a(b = await (0)) {} })', Context.Empty],
46 | ['(class { async constructor(){} })', Context.Empty],
47 | ['\\u0000', Context.Empty],
48 | ['\\u{0}', Context.Empty],
49 | ['a\\u0000', Context.Empty],
50 | ['\\u{110000}', Context.Empty],
51 | ['\\u{FFFFFFF}', Context.Empty],
52 | ['function f(a = super.b){}', Context.Empty],
53 | ['!function f(a = super[0]){}', Context.Empty],
54 | ['!function(a = super.b){}', Context.Empty],
55 | ['function f(a){ super.b }', Context.Empty],
56 | ['class a { b(eval){} };', Context.Empty],
57 | ['!{ a() { function* f(){ super.b(); } } };', Context.Empty],
58 | ['class A { constructor() { {{ (( super() )); }} } }', Context.Empty],
59 | ['class A { constructor() { super(); } }', Context.Empty],
60 | ['class A { *constructor(){} }', Context.Empty],
61 | ['class A extends B { static prototype(){} }', Context.Empty],
62 | ['class A extends B { static set prototype(a) {} }', Context.Empty],
63 | ["function a([]){'use strict';}", Context.Empty],
64 | ['({ a = 0 });', Context.Empty],
65 | ['(...a)', Context.Empty],
66 | ['(a, ...b)', Context.Empty],
67 | ['(((...a)))', Context.Empty],
68 | ['(((a, ...b)))', Context.Empty],
69 | ['0++', Context.Empty],
70 | ['0--', Context.Empty],
71 | ['++0', Context.Empty],
72 | ['--0', Context.Empty],
73 | ['({a: 0} = 0);', Context.Empty],
74 | ['({get a(){}} = 0)', Context.Empty],
75 | ['({set a(b){}} = 0)', Context.Empty],
76 | ['({a(b){}} = 0)', Context.Empty],
77 | ['[0] = 0;', Context.Empty],
78 | ['0 = 0;', Context.Empty],
79 | ['"use strict"; +yield;', Context.Empty],
80 | ['"use strict"; yield:;', Context.Empty],
81 | ['"use strict"; +protected:0;', Context.Empty],
82 | ['"use strict"; +yield:0;', Context.Empty],
83 | ['"use strict"; +async:0;', Context.Empty],
84 | ['"use strict"; function a([yield]){}', Context.Empty],
85 | ['"use strict"; function a({yield}){}', Context.Empty],
86 | ['"use strict"; function a({yield=0}){}', Context.Empty],
87 | ['"use strict"; function a({a:yield}){}', Context.Empty],
88 | ['"use strict"; function a([yield,...a]){}', Context.Empty],
89 | ['"use strict"; class A {set a(yield){}}', Context.Empty],
90 | ['const a;', Context.Empty],
91 | ["const a, b = 0;'", Context.Empty],
92 | ['const a = 0, b;', Context.Empty],
93 | ['{ const a; }', Context.Empty],
94 | ['function f(){ const a; }', Context.Empty],
95 | ['for(const a;;);', Context.Empty],
96 | ['for(const a = 0, b;;);', Context.Empty],
97 | ['for(const let in 0);', Context.Empty],
98 | ['for(let let of 0);', Context.Empty],
99 | ['for(const let of 0);', Context.Empty],
100 | ['{ continue; }', Context.Empty],
101 | ['continue', Context.Empty],
102 | ['if(0) continue;', Context.Empty],
103 | ['while(1) !function(){ break; };', Context.Empty],
104 | ['while(1) !function(){ continue; };', Context.Empty],
105 | ['({ a(){ super(); } });', Context.Empty],
106 | ['for(const a = 1, b;;);', Context.Empty],
107 | ['for([0] of 0);', Context.Empty],
108 | ['[a] *= 0;', Context.Empty],
109 | ['let a, b, c, let;', Context.Empty],
110 | ['"use strict"; +yield', Context.Empty],
111 | ['/[a-z]/z', Context.Empty],
112 | [
113 | `var af = x
114 | => x;`,
115 | Context.Empty
116 | ],
117 | ['async function a(k = super.prop) { }', Context.Empty],
118 | ['(async function(k = super.prop) {})', Context.Empty],
119 | ['(async function a(k = super.prop) {})', Context.Empty],
120 | ['async function a() { super.prop(); }', Context.Empty],
121 | ['(async function a() { super.prop(); })', Context.Empty],
122 | ['(async function a(k = super()) {})', Context.Empty],
123 | ['(async function a() { super(); })', Context.Empty],
124 | ['(async function a(k = await 3) {})', Context.Empty],
125 | ['async function a(k = await 3) {}', Context.Empty],
126 | ['"use strict" var af = (yield) => 1;', Context.Empty],
127 | ['({set a(b){}} = 0)', Context.Empty],
128 | ['for(const a;;);', Context.Empty],
129 | [`"use strict"; var af = package => 1;`, Context.Empty],
130 | ['function a() { "use strict"; var implements; }', Context.Empty],
131 | ["function a([yield,...a]){ 'use strict'; }", Context.Empty],
132 | ['function* a(){ function* b({c = yield}){} }', Context.Empty],
133 | ["function a() { 'use strict'; let = 1; }", Context.Empty],
134 | ['class a extends b { c() { function d(c = super.e()){} } }', Context.Empty],
135 | [` /./ii`, Context.Empty],
136 | [`(a, ...b)`, Context.Empty],
137 | [' for(const a = 1, let = 2;;);', Context.Empty],
138 | [` function a() { "use strict"; private = 1; }`, Context.Empty],
139 | ['({ a(){ super(); } });', Context.Empty],
140 | ['for(const a;;);', Context.Empty],
141 | [`"use strict"; for (a in let) {}`, Context.Empty],
142 | ['for({a: 0} of 0);', Context.Empty],
143 | ['b: break a;', Context.Empty],
144 | ['let a, let = 0;', Context.Empty],
145 | ['let a, let;', Context.Empty],
146 | ['for(let a, let;;);', Context.Empty],
147 | ['"use strict"; +static:0;', Context.Empty],
148 | ['"use strict"; +protected;', Context.Empty],
149 | ['"use strict"; +public;', Context.Empty],
150 | ['"use strict"; +yield;', Context.Empty],
151 | ['"use strict"; +implements:0;', Context.Empty],
152 | ['"use strict"; +interface:0;', Context.Empty],
153 | ['"use strict"; +let:0;', Context.Empty],
154 | ['"use strict"; +package:0;', Context.Empty],
155 | ['"use strict"; +private:0;', Context.Empty],
156 | ['"use strict"; +await:0;', Context.Empty],
157 | ['for(const a = 1;;) c: function b(){}', Context.Empty],
158 | ['!{ a() { function* b(a = super.c()){} } };', Context.Empty],
159 | ['({a(b){}} = 0)', Context.Empty],
160 | ['({a}) = 0;', Context.Empty],
161 | ['class A { constructor(){} constructor(){} }', Context.Empty],
162 | ['class A { constructor(){} "constructor"(){} }', Context.Empty],
163 | ['!class A { constructor(){} constructor(){} }', Context.Empty],
164 | ['!class A { constructor(){} "constructor"(){} }', Context.Empty],
165 | ['class A extends B { static get prototype(){} }', Context.Empty],
166 | ['new.target', Context.Empty],
167 | ['(class {[super.a](){}});', Context.Empty],
168 | ['(class {[super()](){}});', Context.Empty],
169 | ['!{ __proto__: null, __proto__: null, };', Context.Empty],
170 | ['!{ a() { !function* (a = super.b()){} } };', Context.Empty],
171 | ['class A extends B { a() { function* f(a = super.b()){} } }', Context.Empty],
172 | ['class A extends B { a() { !function* (a = super.b()){} } }', Context.Empty]
173 | ]);
174 |
--------------------------------------------------------------------------------
/src/scanner/charClassifier.ts:
--------------------------------------------------------------------------------
1 | import { unicodeLookup } from './unicode';
2 | import { Chars } from './chars';
3 |
4 | export const enum CharFlags {
5 | UnknownChar = 0b00000000000000000000000000000000,
6 | Decimal = 0b00000000000000000000000000000001,
7 | IdentifierStart = 0b00000000000000000000000000000010,
8 | IdentifierPart = 0b00000000000000000000000000000101,
9 | WhiteSpace = 0b00000000000000000000000000001000,
10 | LineTerminator = 0b00000000000000000000000000010000,
11 | Hex = 0b00000000000000000000000000100000,
12 | Underscore = 0b00000000000000000000000001000000
13 | }
14 |
15 | /**
16 | * Lookup table for mapping a codepoint to a set of flags
17 | */
18 | export const CharTypes = [
19 | CharFlags.UnknownChar /* 0x00 */,
20 | CharFlags.UnknownChar /* 0x01 */,
21 | CharFlags.UnknownChar /* 0x02 */,
22 | CharFlags.UnknownChar /* 0x03 */,
23 | CharFlags.UnknownChar /* 0x04 */,
24 | CharFlags.UnknownChar /* 0x05 */,
25 | CharFlags.UnknownChar /* 0x06 */,
26 | CharFlags.UnknownChar /* 0x07 */,
27 | CharFlags.UnknownChar /* 0x08 */,
28 | CharFlags.UnknownChar /* 0x09 */,
29 | CharFlags.LineTerminator /* 0x0A */,
30 | CharFlags.UnknownChar /* 0x0B */,
31 | CharFlags.UnknownChar /* 0x0C */,
32 | CharFlags.LineTerminator /* 0x0D */,
33 | CharFlags.UnknownChar /* 0x0E */,
34 | CharFlags.UnknownChar /* 0x0F */,
35 | CharFlags.UnknownChar /* 0x10 */,
36 | CharFlags.WhiteSpace /* 0x11 */,
37 | CharFlags.UnknownChar /* 0x12 */,
38 | CharFlags.WhiteSpace /* 0x13 */,
39 | CharFlags.WhiteSpace /* 0x14 */,
40 | CharFlags.UnknownChar /* 0x15 */,
41 | CharFlags.UnknownChar /* 0x16 */,
42 | CharFlags.UnknownChar /* 0x17 */,
43 | CharFlags.UnknownChar /* 0x18 */,
44 | CharFlags.UnknownChar /* 0x19 */,
45 | CharFlags.UnknownChar /* 0x1A */,
46 | CharFlags.UnknownChar /* 0x1B */,
47 | CharFlags.UnknownChar /* 0x1C */,
48 | CharFlags.UnknownChar /* 0x1D */,
49 | CharFlags.UnknownChar /* 0x1E */,
50 | CharFlags.UnknownChar /* 0x1F */,
51 | CharFlags.WhiteSpace /* 0x20 */,
52 | CharFlags.UnknownChar /* 0x21 ! */,
53 | CharFlags.UnknownChar /* 0x22 */,
54 | CharFlags.UnknownChar /* 0x23 # */,
55 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x24 $ */,
56 | CharFlags.UnknownChar /* 0x25 % */,
57 | CharFlags.UnknownChar /* 0x26 & */,
58 | CharFlags.UnknownChar /* 0x27 */,
59 | CharFlags.UnknownChar /* 0x28 */,
60 | CharFlags.UnknownChar /* 0x29 */,
61 | CharFlags.UnknownChar /* 0x2A */,
62 | CharFlags.UnknownChar /* 0x2B */,
63 | CharFlags.UnknownChar /* 0x2C */,
64 | CharFlags.UnknownChar /* 0x2D */,
65 | CharFlags.UnknownChar /* 0x2E */,
66 | CharFlags.UnknownChar /* 0x2F */,
67 | CharFlags.Decimal | CharFlags.Hex /* 0x30 0 */,
68 | CharFlags.Decimal | CharFlags.Hex /* 0x31 1 */,
69 | CharFlags.Decimal | CharFlags.Hex /* 0x32 2 */,
70 | CharFlags.Decimal | CharFlags.Hex /* 0x33 3 */,
71 | CharFlags.Decimal | CharFlags.Hex /* 0x34 4 */,
72 | CharFlags.Decimal | CharFlags.Hex /* 0x35 5 */,
73 | CharFlags.Decimal | CharFlags.Hex /* 0x36 6 */,
74 | CharFlags.Decimal | CharFlags.Hex /* 0x37 7 */,
75 | CharFlags.Decimal | CharFlags.Hex /* 0x38 8 */,
76 | CharFlags.Decimal | CharFlags.Hex /* 0x39 9 */,
77 | CharFlags.UnknownChar /* 0x3A */,
78 | CharFlags.UnknownChar /* 0x3B */,
79 | CharFlags.UnknownChar /* 0x3C < */,
80 | CharFlags.UnknownChar /* 0x3D = */,
81 | CharFlags.UnknownChar /* 0x3E > */,
82 | CharFlags.UnknownChar /* 0x3F */,
83 | CharFlags.UnknownChar /* 0x40 @ */,
84 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x41 A */,
85 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x42 B */,
86 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x43 C */,
87 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x44 D */,
88 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x45 E */,
89 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x46 F */,
90 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x47 G */,
91 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x48 H */,
92 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x49 I */,
93 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x4A J */,
94 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x4B K */,
95 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x4C L */,
96 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x4D M */,
97 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x4E N */,
98 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x4F O */,
99 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x50 P */,
100 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x51 Q */,
101 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x52 R */,
102 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x53 S */,
103 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x54 T */,
104 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x55 U */,
105 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x56 V */,
106 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x57 W */,
107 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x58 X */,
108 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x59 Y */,
109 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x5A Z */,
110 | CharFlags.UnknownChar /* 0x5B */,
111 | CharFlags.IdentifierStart /* 0x5C */,
112 | CharFlags.UnknownChar /* 0x5D */,
113 | CharFlags.UnknownChar /* 0x5E */,
114 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Underscore /* 0x5F _ */,
115 | CharFlags.UnknownChar /* 0x60 */,
116 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x61 a */,
117 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x62 b */,
118 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x63 c */,
119 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x64 d */,
120 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x65 e */,
121 | CharFlags.IdentifierStart | CharFlags.IdentifierPart | CharFlags.Hex /* 0x66 f */,
122 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x67 g */,
123 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x68 h */,
124 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x69 i */,
125 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x6A j */,
126 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x6B k */,
127 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x6C l */,
128 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x6D m */,
129 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x6E n */,
130 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x6F o */,
131 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x70 p */,
132 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x71 q */,
133 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x72 r */,
134 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x73 s */,
135 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x74 t */,
136 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x75 u */,
137 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x76 v */,
138 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x77 w */,
139 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x78 x */,
140 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x79 y */,
141 | CharFlags.IdentifierStart | CharFlags.IdentifierPart /* 0x7A z */,
142 | CharFlags.UnknownChar /* 0x7B */,
143 | CharFlags.UnknownChar /* 0x7C */,
144 | CharFlags.UnknownChar /* 0x7D */,
145 | CharFlags.UnknownChar /* 0x7E */,
146 | CharFlags.UnknownChar /* 0x7F */
147 | ];
148 |
149 | export function isIdentifierPart(code: number): any {
150 | /*
151 | * ES2020 11.6 IdentifierPart
152 | * $ (dollar sign)
153 | * _ (underscore)
154 | *
155 | *
156 | * or any character with the Unicode property «ID_Continue».
157 | *
158 | * We use a lookup table for small and thus common characters for speed.
159 | */
160 | return code <= 0x7f
161 | ? CharTypes[code] & CharFlags.IdentifierPart
162 | : (unicodeLookup[(code >>> 5) + 0] >>> code) & 31 & 1 ||
163 | code === Chars.ZeroWidthJoiner ||
164 | code === Chars.ZeroWidthNonJoiner;
165 | }
166 |
--------------------------------------------------------------------------------
/src/scanner/numeric.ts:
--------------------------------------------------------------------------------
1 | import { ParserState, Context, Flags } from '../parser/common';
2 | import { toHex, Chars, CharTypes } from './';
3 | import { Token } from '../token';
4 | import { report, Errors } from '../errors';
5 |
6 | export function scanNumber(parser: ParserState, context: Context, source: string, char: number, isFloat: 0 | 1): Token {
7 | const enum NumberKind {
8 | Empty = 0,
9 | ImplicitOctal = 1 << 0,
10 | Binary = 1 << 1,
11 | Octal = 1 << 2,
12 | Hex = 1 << 3,
13 | Decimal = 1 << 4,
14 | DecimalWithLeadingZero = 1 << 5
15 | }
16 |
17 | let value: string | number = 0;
18 | let skipSMI = isFloat === 0 ? 0 : 1;
19 | let state = NumberKind.Decimal;
20 |
21 | if (isFloat === 1) {
22 | // we know we have at least one digit
23 | value = '.' + scanDecimalDigits(parser, source, char);
24 | char = source.charCodeAt(parser.index);
25 | } else {
26 | let allowSeparator: 0 | 1 = 0;
27 |
28 | if (char === Chars.Zero) {
29 | parser.index++; // skips '0'
30 |
31 | char = source.charCodeAt(parser.index);
32 |
33 | // Hex
34 | if ((char | 32) === Chars.LowerX) {
35 | char = source.charCodeAt(++parser.index); // skips 'X', 'x'
36 |
37 | while ((CharTypes[char] & 0b00000000000000000000000001100000) > 0) {
38 | if (char === Chars.Underscore) {
39 | if (allowSeparator === 0) report(parser, Errors.ContinuousNumericSeparator);
40 | allowSeparator = 0;
41 | } else {
42 | allowSeparator = 1;
43 | value = value * 0x10 + toHex(char);
44 | }
45 | char = source.charCodeAt(++parser.index);
46 | }
47 |
48 | if (allowSeparator === 0) report(parser, Errors.TrailingNumericSeparator);
49 |
50 | state = NumberKind.Hex;
51 | } else if ((char | 32) === Chars.LowerB) {
52 | char = source.charCodeAt(++parser.index); // skips 'B', 'b'
53 |
54 | while ((char >= Chars.Zero && char <= Chars.One) || char === Chars.Underscore) {
55 | if (char === Chars.Underscore) {
56 | if (allowSeparator === 0) report(parser, Errors.ContinuousNumericSeparator);
57 | allowSeparator = 0;
58 | } else {
59 | allowSeparator = 1;
60 | value = value * 2 + (char - Chars.Zero);
61 | }
62 | char = source.charCodeAt(++parser.index);
63 | }
64 |
65 | if (allowSeparator === 0) report(parser, Errors.TrailingNumericSeparator);
66 |
67 | state = NumberKind.Binary;
68 | } else if ((char | 32) === Chars.LowerO) {
69 | char = source.charCodeAt(++parser.index); // skips 'X', 'x'
70 |
71 | while ((char >= Chars.Zero && char <= Chars.Seven) || char === Chars.Underscore) {
72 | if (char === Chars.Underscore) {
73 | if (allowSeparator === 0) report(parser, Errors.ContinuousNumericSeparator);
74 | allowSeparator = 0;
75 | } else {
76 | allowSeparator = 1;
77 | value = value * 8 + (char - Chars.Zero);
78 | }
79 | char = source.charCodeAt(++parser.index);
80 | }
81 |
82 | if (allowSeparator === 0) report(parser, Errors.TrailingNumericSeparator);
83 |
84 | state = NumberKind.Octal;
85 | } else if (char >= Chars.Zero && char <= Chars.Eight) {
86 | // Octal integer literals are not permitted in strict mode code
87 | if (context & Context.Strict) report(parser, Errors.StrictOctalEscape);
88 |
89 | state = NumberKind.ImplicitOctal;
90 |
91 | while (char >= Chars.Zero && char <= Chars.Nine) {
92 | if (char >= Chars.Eight && char <= Chars.Nine) {
93 | state = NumberKind.DecimalWithLeadingZero;
94 | skipSMI = 1;
95 | break;
96 | }
97 | value = value * 8 + (char - Chars.Zero);
98 | char = source.charCodeAt(++parser.index);
99 | }
100 |
101 | if (char === Chars.Underscore) report(parser, Errors.TrailingNumericSeparator);
102 |
103 | if (char === Chars.LowerN) report(parser, Errors.InvalidBigIntLiteral);
104 |
105 | parser.flags |= Flags.Octals;
106 | } else if (char >= Chars.Eight && char <= Chars.Nine) {
107 | state = NumberKind.DecimalWithLeadingZero;
108 | } else if (char === Chars.Underscore) {
109 | report(parser, Errors.TrailingNumericSeparator);
110 | }
111 | }
112 |
113 | // Parse decimal digits and allow trailing fractional part.
114 | if ((state & 0b00000000000000000000000000110000) > 0) {
115 | // This is an optimization for parsing Decimal numbers as Smi's.
116 |
117 | if (skipSMI === 0) {
118 | let digit = 9;
119 | // Optimization: most decimal values fit into 4 bytes.
120 | while ((char <= Chars.Nine && char >= Chars.Zero && digit >= 0) || char === Chars.Underscore) {
121 | if (char === Chars.Underscore) {
122 | char = source.charCodeAt(++parser.index);
123 | if (
124 | char === Chars.Underscore ||
125 | (state & NumberKind.DecimalWithLeadingZero) === NumberKind.DecimalWithLeadingZero
126 | ) {
127 | report(parser, Errors.ContinuousNumericSeparator);
128 | }
129 | allowSeparator = 1;
130 | continue;
131 | }
132 | allowSeparator = 0;
133 | value = value * 10 + (char - Chars.Zero);
134 | char = source.charCodeAt(++parser.index);
135 | --digit;
136 | }
137 |
138 | if (allowSeparator === 1) report(parser, Errors.TrailingNumericSeparator);
139 |
140 | if (digit >= 0 && char !== Chars.Period && (CharTypes[char] & 0b00000000000000000000000000000011) === 0) {
141 | // Most numbers are pure decimal integers without fractional component
142 | // or exponential notation - handle that with optimized code
143 | parser.tokenValue = value;
144 | return Token.NumericLiteral;
145 | }
146 | }
147 |
148 | value += scanDecimalDigits(parser, source, char) as any;
149 |
150 | char = source.charCodeAt(parser.index);
151 |
152 | // Consume the decimal dot
153 | if (char === Chars.Period) {
154 | isFloat = 1;
155 | char = source.charCodeAt(++parser.index);
156 | if (char === Chars.Underscore) report(parser, Errors.ContinuousNumericSeparator);
157 | value += ('.' + scanDecimalDigits(parser, source, char)) as any;
158 | char = source.charCodeAt(parser.index);
159 | }
160 | }
161 | }
162 |
163 | if (char === Chars.LowerN && isFloat === 0 && (state & 0b00000000000000000000000000011110) > 0) {
164 | parser.index++;
165 | return Token.BigIntLiteral;
166 | }
167 |
168 | if ((char | 32) === Chars.LowerE) {
169 | const end = parser.index;
170 |
171 | char = source.charCodeAt(++parser.index);
172 |
173 | // '-', '+'
174 | if (char === Chars.Hyphen || char === Chars.Plus) char = source.charCodeAt(++parser.index);
175 |
176 | if (char === Chars.Underscore) report(parser, Errors.TrailingNumericSeparator);
177 |
178 | // Exponential notation must contain at least one digit
179 | if ((state & 0b00000000000000000000000000110000) === 0) report(parser, Errors.MissingExponent);
180 |
181 | const start = parser.index;
182 |
183 | // Consume exponential digits
184 | value += parser.source.substring(end, parser.index) + scanDecimalDigits(parser, source, char);
185 |
186 | if (start === parser.index) report(parser, Errors.MissingExponent);
187 |
188 | char = source.charCodeAt(parser.index);
189 | }
190 |
191 | if ((CharTypes[char] & 0b00000000000000000000000000000011) > 0) report(parser, Errors.IDStartAfterNumber);
192 |
193 | parser.tokenValue =
194 | (state & 0b00000000000000000000000000001111) > 0 ? value : isFloat === 1 ? parseFloat(value as string) : +value;
195 |
196 | return Token.NumericLiteral;
197 | }
198 |
199 | export function scanDecimalDigits(parser: ParserState, source: string, char: number) {
200 | let allowSeparator: 0 | 1 = 0;
201 | let value = '';
202 | let start = parser.index;
203 | while ((char <= Chars.Nine && char >= Chars.Zero) || char === Chars.Underscore) {
204 | if (char === Chars.Underscore) {
205 | if (source.charCodeAt(parser.index + 1) === Chars.Underscore) report(parser, Errors.ContinuousNumericSeparator);
206 | value += source.substring(start, parser.index);
207 | char = source.charCodeAt(++parser.index);
208 | allowSeparator = 1;
209 | start = parser.index;
210 | continue;
211 | }
212 | allowSeparator = 0;
213 | char = source.charCodeAt(++parser.index);
214 | }
215 |
216 | if (allowSeparator === 1) report(parser, Errors.TrailingNumericSeparator);
217 |
218 | return value + source.substring(start, parser.index);
219 | }
220 |
--------------------------------------------------------------------------------
/test/scanner/comments.ts:
--------------------------------------------------------------------------------
1 | import * as t from 'assert';
2 | import { Context } from '../../src/parser/common';
3 | import { scan } from '../../src/scanner/scan';
4 | import { create } from '../../src/parser/core';
5 | import { Token } from '../../src/token';
6 |
7 | describe('Scanner - comments', () => {
8 | function fail(name: string, source: string, context: Context) {
9 | it(name, () => {
10 | const parser = create(source);
11 | t.throws(() => scan(parser, context, source, 1, source.length, Token.EOF, 0, true, /* allowRegExp */ 0));
12 | });
13 | }
14 |
15 | fail('fails on /*CHECK#1/', '/*CHECK#1/', Context.Module | Context.Strict);
16 | fail('fails on -->', '-->', Context.Module | Context.Strict);
17 | fail('fails on ', '-->', Context.OptionsDisableWebCompat);
19 | fail('fails on ',
119 | hasNext: false,
120 | line: 1,
121 | column: 6
122 | });
123 |
124 | passAll(
125 | lt => `skips ${lt}s`,
126 | lt => ({
127 | source: `${lt}${lt}${lt}${lt}${lt}${lt}${lt}${lt}`,
128 |
129 | hasNext: false,
130 | line: 9,
131 | column: 0
132 | })
133 | );
134 |
135 | pass('skips single line comment + HTML comments', {
136 | source: '// SHOULD SKIP THIS -->',
137 | hasNext: false,
138 | line: 1,
139 | column: 23
140 | });
141 |
142 | pass('skips 2x single line comment and HTML comment', {
143 | source: '// --> // foo',
144 | hasNext: false,
145 | line: 1,
146 | column: 14
147 | });
148 |
149 | pass('skips multiline comments with line terminator', {
150 | source: '/* \r\n */',
151 | hasNext: false,
152 | line: 2,
153 | column: 3
154 | });
155 |
156 | passAll(
157 | lt => `skips single line comments with ${lt}`,
158 | lt => ({
159 | source: ` \t // foo bar${lt} `,
160 | hasNext: false,
161 | line: 2,
162 | column: 2
163 | })
164 | );
165 |
166 | passAll(
167 | lt => `skips multiple single line comments with ${lt}`,
168 | lt => ({
169 | source: ` \t // foo bar${lt} // baz ${lt} //`,
170 |
171 | hasNext: false,
172 | line: 3,
173 | column: 3
174 | })
175 | );
176 |
177 | pass('skips multiline comments with nothing', {
178 | source: ' \t /* foo * /* bar */ ',
179 | hasNext: false,
180 | line: 1,
181 | column: 24
182 | });
183 |
184 | passAll(
185 | lt => `skips multiline comments with ${lt}`,
186 | lt => ({
187 | source: ` \t /* foo * /* bar ${lt} */ `,
188 | hasNext: false,
189 | line: 2,
190 | column: 5
191 | })
192 | );
193 |
194 | passAll(
195 | lt => `skips multiple multiline comments with ${lt}`,
196 | lt => ({
197 | source: ` \t /* foo bar${lt} *//* baz*/ ${lt} /**/`,
198 | hasNext: false,
199 | line: 3,
200 | column: 5
201 | })
202 | );
203 |
204 | passAll(
205 | lt => `skips HTML single line comments with ${lt}`,
206 | lt => ({
207 | source: ` \t `,
228 | hasNext: false,
229 | line: 2,
230 | column: 5
231 | })
232 | );
233 |
234 | passAll(
235 | lt => `skips line of single HTML close comment after ${lt}`,
236 | lt => ({
237 | source: ` \t ${lt}--> the comment extends to these characters${lt} `,
238 | hasNext: false,
239 | line: 3,
240 | column: 1
241 | })
242 | );
243 |
244 | passAll(
245 | lt => `allows HTML close comment after ${lt} + WS`,
246 | lt => ({
247 | source: ` \t ${lt} --> the comment extends to these characters${lt} `,
248 | hasNext: false,
249 | line: 3,
250 | column: 1
251 | })
252 | );
253 |
254 | passAll(
255 | lt => `skips single-line block on line of HTML close after ${lt}`,
256 | lt => ({
257 | source: ` \t /*${lt}*/ /* optional SingleLineDelimitedCommentSequence */ ${''}--> the comment extends to these characters${lt} `,
258 | hasNext: false,
259 | line: 3,
260 | column: 1
261 | })
262 | );
263 |
264 | passAll(
265 | lt => `skips 2 single-line block on line of HTML close after ${lt}`,
266 | lt => ({
267 | source: ` \t /*${lt}*/ /**/ /* second optional ${''}SingleLineDelimitedCommentSequence */ ${''}--> the comment extends to these characters${lt} `,
268 | hasNext: false,
269 | line: 3,
270 | column: 1
271 | })
272 | );
273 |
274 | passAll(
275 | lt => `skips block HTML close with ${lt} + empty line`,
276 | lt => ({
277 | source: ` \t /*${lt}*/ -->${lt} `,
278 | hasNext: false,
279 | line: 3,
280 | column: 1
281 | })
282 | );
283 |
284 | passAll(
285 | lt => `skips block HTML close with ${lt}`,
286 | lt => ({
287 | source: ` \t /*${lt}*/ --> the comment extends to these characters${lt} `,
288 | hasNext: false,
289 | line: 3,
290 | column: 1
291 | })
292 | );
293 |
294 | passAll(
295 | lt => `skips first line block HTML close with ${lt}`,
296 | lt => ({
297 | source: ` \t /* optional FirstCommentLine ${lt}*/ --> ` + `the comment extends to these characters${lt} `,
298 | hasNext: false,
299 | line: 3,
300 | column: 1
301 | })
302 | );
303 |
304 | passAll(
305 | lt => `skips multi block + HTML close with ${lt}`,
306 | lt => ({
307 | source: ` \t /*${lt}optional${lt}MultiLineCommentChars ${lt}*/ --> the comment extends to these characters${lt} `,
308 | hasNext: false,
309 | line: 5,
310 | column: 1
311 | })
312 | );
313 |
314 | passAll(
315 | lt => `skips multi block + single block + HTML close with ${lt}`,
316 | lt => ({
317 | source: ` \t /*${lt}*/ /* optional SingleLineDelimitedCommentSequence ${lt}*/ --> the comment extends to these characters${lt} `,
318 | hasNext: false,
319 | line: 4,
320 | column: 1
321 | })
322 | );
323 |
324 | passAll(
325 | lt => `skips multi block + 2 single block + HTML close with ${lt}`,
326 | lt => ({
327 | source: ` \t /*${lt}*/ /**/ /* optional SingleLineDelimitedCommentSequence ${lt}*/ --> the comment extends to these characters${lt} `,
328 | hasNext: false,
329 | line: 4,
330 | column: 1
331 | })
332 | );
333 | });
334 |
--------------------------------------------------------------------------------
/test/scanner/string.ts:
--------------------------------------------------------------------------------
1 | import * as t from 'assert';
2 | import { Context } from '../../src/parser/common';
3 | import { create } from '../../src/parser/core';
4 | import { Token } from '../../src/token';
5 | import { scan } from '../../src/scanner/scan';
6 |
7 | describe('Scanner - String literal', () => {
8 | const tokens: Array<[Context, Token, string, string]> = [
9 | [Context.OptionsRaw, Token.StringLiteral, '"abc"', 'abc'],
10 | [Context.OptionsRaw, Token.StringLiteral, '"\\123"', 'S'],
11 | [Context.OptionsRaw, Token.StringLiteral, '"12abc"', '12abc'],
12 | [Context.OptionsRaw, Token.StringLiteral, '"\\7771"', '?71'],
13 | [Context.OptionsRaw, Token.StringLiteral, '"\\0"', '\u0000'],
14 | [Context.OptionsRaw, Token.StringLiteral, '"\\u{89abc}"', 'Ȧʼ'],
15 | [Context.OptionsRaw, Token.StringLiteral, '"\\u{CDEF}"', '췯'],
16 | [Context.OptionsRaw, Token.StringLiteral, '"\\u{0000000000000000000010ffff}"', 'пϿ'],
17 | [Context.OptionsRaw, Token.StringLiteral, '"\\u{10ffff}"', 'пϿ'],
18 | [Context.OptionsRaw, Token.StringLiteral, '"\\u1000"', 'က'],
19 | [Context.OptionsRaw | Context.Strict, Token.StringLiteral, '"\\0"', '\u0000'],
20 | [Context.OptionsRaw, Token.StringLiteral, '";"', ';'],
21 | [Context.OptionsRaw, Token.StringLiteral, '"\\r"', '\r'],
22 | [Context.OptionsRaw, Token.StringLiteral, '""', ''],
23 | [Context.OptionsRaw, Token.StringLiteral, '"123"', '123'],
24 | [Context.OptionsRaw, Token.StringLiteral, '"true"', 'true'],
25 | [Context.OptionsRaw, Token.StringLiteral, '"a\\u0061\\x62\\143"', 'aabc'],
26 | [Context.OptionsRaw, Token.StringLiteral, '"\\b\\f\\n\\r\\t\\va"', '\b\f\n\r\t\u000ba'],
27 | [Context.OptionsRaw, Token.StringLiteral, '"\\05"', '\u0005'],
28 | [Context.OptionsRaw, Token.StringLiteral, '"\\u{0f3b}"', '༻'],
29 | [Context.OptionsRaw, Token.StringLiteral, '"\
30 | "', ' '],
31 |
32 | // Russian letters
33 | [Context.OptionsRaw, Token.StringLiteral, '"\\б"', 'б'],
34 | [Context.OptionsRaw, Token.StringLiteral, '"\\И"', 'И'],
35 | [Context.OptionsRaw, Token.StringLiteral, '"\\Й"', 'Й'],
36 | [Context.OptionsRaw, Token.StringLiteral, '"\\К"', 'К'],
37 | [Context.OptionsRaw, Token.StringLiteral, '"\\Л"', 'Л'],
38 | [Context.OptionsRaw, Token.StringLiteral, '"\\О"', 'О'],
39 | [Context.OptionsRaw, Token.StringLiteral, '"\\Ф"', 'Ф'],
40 | [Context.OptionsRaw, Token.StringLiteral, '"\\Ц"', 'Ц'],
41 | [Context.OptionsRaw, Token.StringLiteral, '"\\Ш"', 'Ш'],
42 | [Context.OptionsRaw, Token.StringLiteral, '"\\Э"', 'Э'],
43 | [Context.OptionsRaw, Token.StringLiteral, '"\\ж"', 'ж'],
44 | [Context.OptionsRaw, Token.StringLiteral, '"\\з"', 'з'],
45 |
46 | // Escaped letters
47 | [Context.OptionsRaw, Token.StringLiteral, '"\\b"', '\b'],
48 | [Context.OptionsRaw, Token.StringLiteral, '"\\v"', '\v'],
49 | [Context.OptionsRaw, Token.StringLiteral, '"\\t"', '\t'],
50 | [Context.OptionsRaw, Token.StringLiteral, '"\\f"', '\f'],
51 | [Context.OptionsRaw, Token.StringLiteral, '"\\j"', 'j'],
52 | [Context.OptionsRaw, Token.StringLiteral, '"\\A"', 'A'],
53 | [Context.OptionsRaw, Token.StringLiteral, '"\\t"', '\t'],
54 | [Context.OptionsRaw, Token.StringLiteral, '"\\fsuffix"', '\fsuffix'],
55 | [Context.OptionsRaw, Token.StringLiteral, '"\\Rsuffix"', 'Rsuffix'],
56 | [Context.OptionsRaw, Token.StringLiteral, '"prefix\\r\\n"', 'prefix\r\n'],
57 |
58 | // Unicode escape sequence
59 |
60 | [Context.OptionsRaw, Token.StringLiteral, '"\\u1000"', 'က'],
61 | [Context.OptionsRaw, Token.StringLiteral, '"\\uf2ff"', ''],
62 | [Context.OptionsRaw, Token.StringLiteral, '"\\u0041"', 'A'],
63 | [Context.OptionsRaw, Token.StringLiteral, '"\\uf2ff"', ''],
64 | [Context.OptionsRaw, Token.StringLiteral, '"\\u0123"', 'ģ'],
65 | [Context.OptionsRaw, Token.StringLiteral, '"\\u0123 postfix"', 'ģ postfix'],
66 | [Context.OptionsRaw, Token.StringLiteral, '"\\u{89abc}"', 'Ȧʼ'],
67 | [Context.OptionsRaw, Token.StringLiteral, '"\\u{CDEF}"', '췯'],
68 | [Context.OptionsRaw, Token.StringLiteral, '"\\u{0000000000000000000010ffff}"', 'пϿ'],
69 | [Context.OptionsRaw, Token.StringLiteral, '"\\u{10ffff}"', 'пϿ'],
70 | [Context.OptionsRaw, Token.StringLiteral, '"\\u0062"', 'b'],
71 | [Context.OptionsRaw, Token.StringLiteral, '"\\u0410"', 'А'],
72 | [Context.OptionsRaw, Token.StringLiteral, '"\\u0412"', 'В'],
73 | [Context.OptionsRaw, Token.StringLiteral, '"\\u0419"', 'Й'],
74 | [Context.OptionsRaw, Token.StringLiteral, '"\\u042E"', 'Ю'],
75 | [Context.OptionsRaw, Token.StringLiteral, '"\\u0432"', 'в'],
76 | [Context.OptionsRaw, Token.StringLiteral, '"\\u0030"', '0'],
77 | [Context.OptionsRaw, Token.StringLiteral, '"\\u0035"', '5'],
78 | [Context.OptionsRaw, Token.StringLiteral, '"\\u0003"', '\u0003'],
79 | [Context.OptionsRaw, Token.StringLiteral, '"\\u180E"', ''],
80 |
81 | // Escaped hex
82 |
83 | [Context.OptionsRaw, Token.StringLiteral, '"\\x01F"', '\u0001F'],
84 | [Context.OptionsRaw, Token.StringLiteral, '"\\x05B"', '\u0005B'],
85 | [Context.OptionsRaw, Token.StringLiteral, '"\\x0D3"', '\r3'],
86 | [Context.OptionsRaw, Token.StringLiteral, '"\\x088"', '\b8'],
87 | [Context.OptionsRaw, Token.StringLiteral, '"\\x34"', '4'],
88 | [Context.OptionsRaw, Token.StringLiteral, '"\\xCd"', 'Í'],
89 | [Context.OptionsRaw, Token.StringLiteral, '"\\xF0"', 'ð'],
90 | [
91 | Context.OptionsRaw,
92 | Token.StringLiteral,
93 | '"\\xF000111FEEEDDAAAB77777999344BBBCCD0"',
94 | 'ð00111FEEEDDAAAB77777999344BBBCCD0'
95 | ],
96 | [Context.OptionsRaw, Token.StringLiteral, '"\\x128"', '\u00128'],
97 | [Context.OptionsRaw, Token.StringLiteral, '"\\xCd#"', 'Í#'],
98 | [Context.OptionsRaw, Token.StringLiteral, '"\\xDe\\x00"', 'Þ\u0000'],
99 | [Context.OptionsRaw, Token.StringLiteral, '"\\0x0061"', '\u0000x0061'],
100 | [Context.OptionsRaw, Token.StringLiteral, '"\\x41"', 'A'],
101 | [Context.OptionsRaw, Token.StringLiteral, '"\\x4A"', 'J'],
102 | [Context.OptionsRaw, Token.StringLiteral, '"\\x4F"', 'O'],
103 | [Context.OptionsRaw, Token.StringLiteral, '"\\x69"', 'i'],
104 |
105 | // Escaped octals
106 | [Context.OptionsRaw, Token.StringLiteral, '"\\01"', '\u0001'],
107 | [Context.OptionsRaw, Token.StringLiteral, '"\\023"', '\u0013'],
108 | [Context.OptionsRaw, Token.StringLiteral, '"\\04"', '\u0004'],
109 | [Context.OptionsRaw, Token.StringLiteral, '"\\44444444444"', '$444444444'],
110 | [Context.OptionsRaw, Token.StringLiteral, '"\\777777"', '?7777'],
111 | [Context.OptionsRaw, Token.StringLiteral, '"\\052"', '*'],
112 | [Context.OptionsRaw, Token.StringLiteral, '"\\08"', '\u00008'],
113 | [Context.OptionsRaw, Token.StringLiteral, '"\\7"', '\u0007'],
114 | [Context.OptionsRaw, Token.StringLiteral, '"\\052"', '*'],
115 | [Context.OptionsRaw, Token.StringLiteral, '"Hello\\nworld"', 'Hello\nworld'],
116 | [Context.OptionsRaw, Token.StringLiteral, '"Hello\\312World"', 'HelloÊWorld'],
117 | [Context.OptionsRaw, Token.StringLiteral, '"Hello\\712World"', 'Hello92World'],
118 | [Context.OptionsRaw, Token.StringLiteral, '"Hello\\1World"', 'Hello\u0001World'],
119 | [Context.OptionsRaw, Token.StringLiteral, '"Hello\\02World"', 'Hello\u0002World'],
120 | [Context.OptionsRaw, Token.StringLiteral, '"\\46"', '&'],
121 | [Context.OptionsRaw, Token.StringLiteral, '"\\5*"', '\u0005*'],
122 | [Context.OptionsRaw, Token.StringLiteral, '"\\10"', '\b'],
123 | [Context.OptionsRaw, Token.StringLiteral, '"\\02"', '\u0002'],
124 | [Context.OptionsRaw, Token.StringLiteral, '"\\02a"', '\u0002a'],
125 | [Context.OptionsRaw, Token.StringLiteral, '"\\02a"', '\u0002a'],
126 | [Context.OptionsRaw, Token.StringLiteral, '"\\73"', ';'],
127 | [Context.OptionsRaw, Token.StringLiteral, '"\\62a"', '2a'],
128 | [Context.OptionsRaw, Token.StringLiteral, '"\\023"', '\u0013'],
129 | [Context.OptionsRaw, Token.StringLiteral, '"\\7"', '\u0007'],
130 | [Context.OptionsRaw, Token.StringLiteral, '"\\012"', '\n'],
131 | [Context.OptionsRaw, Token.StringLiteral, '"\\126"', 'V'],
132 | [Context.OptionsRaw, Token.StringLiteral, '"\\302"', 'Â'],
133 | [Context.OptionsRaw, Token.StringLiteral, '"\\000"', '\u0000'],
134 | [Context.OptionsRaw, Token.StringLiteral, '"\\104"', 'D'],
135 | [Context.OptionsRaw, Token.StringLiteral, '"\\221"', '']
136 | ];
137 |
138 | for (const [ctx, token, op, value] of tokens) {
139 | it(`scans '${op}' at the end`, () => {
140 | const parser = create(op);
141 | const found = scan(parser, ctx, op, 1, op.length, Token.EOF, 0, true, /* allowRegExp */ 0);
142 |
143 | t.deepEqual(
144 | {
145 | token: found,
146 | hasNext: parser.index < parser.length,
147 | line: parser.curLine,
148 | value: parser.tokenValue,
149 | raw: parser.source.slice(parser.start, parser.index),
150 | column: parser.index - parser.offset
151 | },
152 | {
153 | token: token,
154 | hasNext: false,
155 | value: value,
156 | raw: op,
157 | line: 1,
158 | column: op.length
159 | }
160 | );
161 | });
162 |
163 | it(`scans '${op}' with more to go`, () => {
164 | const parser = create(`${op} rest`);
165 | const found = scan(parser, ctx, op, 1, op.length, Token.EOF, 0, true, /* allowRegExp */ 0);
166 |
167 | t.deepEqual(
168 | {
169 | token: found,
170 | hasNext: parser.index < parser.source.length,
171 | raw: parser.source.slice(parser.start, parser.index),
172 | line: parser.curLine,
173 | column: parser.index - parser.offset
174 | },
175 | {
176 | token,
177 | hasNext: true,
178 | raw: op,
179 | line: 1,
180 | column: op.length
181 | }
182 | );
183 | });
184 | }
185 | });
186 |
--------------------------------------------------------------------------------
/test/parser/statements/do-while.ts:
--------------------------------------------------------------------------------
1 | import { pass, fail } from '../core';
2 | import { Context } from '../../../src/parser/common';
3 |
4 | fail('Statements - Do while (fail)', [
5 | ['do foo while (bar);', Context.OptionsDisableWebCompat],
6 | ['do async \n f(){}; while (y)', Context.OptionsDisableWebCompat],
7 | ['do let x = 1; while (false)', Context.Empty],
8 | ['do async \n f(){}; while (y)', Context.Empty],
9 | ['do x, y while (z)', Context.Empty],
10 | ['do foo while (bar);', Context.Empty],
11 | ['do ()=>x while(c)', Context.Empty],
12 | [
13 | `do
14 | a
15 | b
16 | while(c);`,
17 | Context.Empty
18 | ],
19 | ['do let {} = y', Context.Empty],
20 | ['do debugger while(x) x', Context.Empty],
21 | ['do x: function s(){}while(y)', Context.OptionsDisableWebCompat],
22 | [
23 | `do throw function (v, h) {
24 | "use strict"
25 | } while ((""))`,
26 | Context.Empty
27 | ]
28 | ]);
29 |
30 | pass('Statements - Do while (pass)', [
31 | [
32 | `do if (x) {} while(x) x`,
33 | Context.OptionsNext | Context.OptionsLoc,
34 | {
35 | type: 'Program',
36 | sourceType: 'script',
37 | body: [
38 | {
39 | type: 'DoWhileStatement',
40 | body: {
41 | type: 'IfStatement',
42 | test: {
43 | type: 'Identifier',
44 | name: 'x',
45 | start: 7,
46 | end: 8,
47 | loc: {
48 | start: {
49 | line: 1,
50 | column: 7
51 | },
52 | end: {
53 | line: 1,
54 | column: 8
55 | }
56 | }
57 | },
58 | consequent: {
59 | type: 'BlockStatement',
60 | body: [],
61 | start: 10,
62 | end: 12,
63 | loc: {
64 | start: {
65 | line: 1,
66 | column: 10
67 | },
68 | end: {
69 | line: 1,
70 | column: 12
71 | }
72 | }
73 | },
74 | alternate: null,
75 | start: 3,
76 | end: 12,
77 | loc: {
78 | start: {
79 | line: 1,
80 | column: 3
81 | },
82 | end: {
83 | line: 1,
84 | column: 12
85 | }
86 | }
87 | },
88 | start: 0,
89 | test: {
90 | type: 'Identifier',
91 | name: 'x',
92 | start: 19,
93 | end: 20,
94 | loc: {
95 | start: {
96 | line: 1,
97 | column: 19
98 | },
99 | end: {
100 | line: 1,
101 | column: 20
102 | }
103 | }
104 | },
105 | end: 21,
106 | loc: {
107 | start: {
108 | line: 1,
109 | column: 0
110 | },
111 | end: {
112 | line: 1,
113 | column: 21
114 | }
115 | }
116 | },
117 | {
118 | type: 'ExpressionStatement',
119 | expression: {
120 | type: 'Identifier',
121 | name: 'x',
122 | start: 22,
123 | end: 23,
124 | loc: {
125 | start: {
126 | line: 1,
127 | column: 22
128 | },
129 | end: {
130 | line: 1,
131 | column: 23
132 | }
133 | }
134 | },
135 | start: 22,
136 | end: 23,
137 | loc: {
138 | start: {
139 | line: 1,
140 | column: 22
141 | },
142 | end: {
143 | line: 1,
144 | column: 23
145 | }
146 | }
147 | }
148 | ],
149 | start: 0,
150 | end: 23,
151 | loc: {
152 | start: {
153 | line: 1,
154 | column: 0
155 | },
156 | end: {
157 | line: 1,
158 | column: 23
159 | }
160 | }
161 | }
162 | ],
163 | [
164 | `do; while (1)`,
165 | Context.OptionsNext | Context.OptionsLoc,
166 | {
167 | body: [
168 | {
169 | body: {
170 | end: 3,
171 | loc: {
172 | end: {
173 | column: 3,
174 | line: 1
175 | },
176 | start: {
177 | column: 2,
178 | line: 1
179 | }
180 | },
181 | start: 2,
182 | type: 'EmptyStatement'
183 | },
184 | end: 13,
185 | loc: {
186 | end: {
187 | column: 13,
188 | line: 1
189 | },
190 | start: {
191 | column: 0,
192 | line: 1
193 | }
194 | },
195 | start: 0,
196 | test: {
197 | end: 12,
198 | loc: {
199 | end: {
200 | column: 12,
201 | line: 1
202 | },
203 | start: {
204 | column: 11,
205 | line: 1
206 | }
207 | },
208 | start: 11,
209 | type: 'Literal',
210 | value: 1
211 | },
212 | type: 'DoWhileStatement'
213 | }
214 | ],
215 | end: 13,
216 | loc: {
217 | end: {
218 | column: 13,
219 | line: 1
220 | },
221 | start: {
222 | column: 0,
223 | line: 1
224 | }
225 | },
226 | sourceType: 'script',
227 | start: 0,
228 | type: 'Program'
229 | }
230 | ],
231 | [
232 | `do;while(0)return`,
233 | Context.OptionsGlobalReturn | Context.OptionsLoc,
234 | {
235 | body: [
236 | {
237 | body: {
238 | end: 3,
239 | loc: {
240 | end: {
241 | column: 3,
242 | line: 1
243 | },
244 | start: {
245 | column: 2,
246 | line: 1
247 | }
248 | },
249 | start: 2,
250 | type: 'EmptyStatement'
251 | },
252 | end: 11,
253 | loc: {
254 | end: {
255 | column: 11,
256 | line: 1
257 | },
258 | start: {
259 | column: 0,
260 | line: 1
261 | }
262 | },
263 | start: 0,
264 | test: {
265 | end: 10,
266 | loc: {
267 | end: {
268 | column: 10,
269 | line: 1
270 | },
271 | start: {
272 | column: 9,
273 | line: 1
274 | }
275 | },
276 | start: 9,
277 | type: 'Literal',
278 | value: 0
279 | },
280 | type: 'DoWhileStatement'
281 | },
282 | {
283 | argument: null,
284 | end: 17,
285 | loc: {
286 | end: {
287 | column: 17,
288 | line: 1
289 | },
290 | start: {
291 | column: 11,
292 | line: 1
293 | }
294 | },
295 | start: 11,
296 | type: 'ReturnStatement'
297 | }
298 | ],
299 | end: 17,
300 | loc: {
301 | end: {
302 | column: 17,
303 | line: 1
304 | },
305 | start: {
306 | column: 0,
307 | line: 1
308 | }
309 | },
310 | sourceType: 'script',
311 | start: 0,
312 | type: 'Program'
313 | }
314 | ],
315 | [
316 | `do async \n while (y)`,
317 | Context.OptionsNext | Context.OptionsLoc,
318 | {
319 | body: [
320 | {
321 | body: {
322 | end: 8,
323 | expression: {
324 | end: 8,
325 | loc: {
326 | end: {
327 | column: 8,
328 | line: 1
329 | },
330 | start: {
331 | column: 3,
332 | line: 1
333 | }
334 | },
335 | name: 'async',
336 | start: 3,
337 | type: 'Identifier'
338 | },
339 | loc: {
340 | end: {
341 | column: 8,
342 | line: 1
343 | },
344 | start: {
345 | column: 3,
346 | line: 1
347 | }
348 | },
349 | start: 3,
350 | type: 'ExpressionStatement'
351 | },
352 | end: 20,
353 | loc: {
354 | end: {
355 | column: 10,
356 | line: 2
357 | },
358 | start: {
359 | column: 0,
360 | line: 1
361 | }
362 | },
363 | start: 0,
364 | test: {
365 | end: 19,
366 | loc: {
367 | end: {
368 | column: 9,
369 | line: 2
370 | },
371 | start: {
372 | column: 8,
373 | line: 2
374 | }
375 | },
376 | name: 'y',
377 | start: 18,
378 | type: 'Identifier'
379 | },
380 | type: 'DoWhileStatement'
381 | }
382 | ],
383 | end: 20,
384 | loc: {
385 | end: {
386 | column: 10,
387 | line: 2
388 | },
389 | start: {
390 | column: 0,
391 | line: 1
392 | }
393 | },
394 | sourceType: 'script',
395 | start: 0,
396 | type: 'Program'
397 | }
398 | ]
399 | ]);
400 |
--------------------------------------------------------------------------------
/test/parser/miscellaneous/eval-arguments.ts:
--------------------------------------------------------------------------------
1 | import { Context } from '../../../src/parser/common';
2 | import { parseRoot } from '../../../src/seafox';
3 | import * as t from 'assert';
4 |
5 | for (const arg of [
6 | 'arguments--;',
7 | '{ eval }',
8 | '{ arguments }',
9 | '{ foo: eval }',
10 | '{ foo: arguments }',
11 | '(eval) => { "use strict"; }',
12 | '{ eval = 0 }',
13 | '{ arguments = 0 }',
14 | '{ foo: eval = 0 }',
15 | '{ foo: arguments = 0 }',
16 | '[ eval ]',
17 | '[ arguments ]',
18 | '[ eval = 0 ]',
19 | '[ arguments = 0 ]',
20 | '{ x: (eval) }',
21 | '{ x: (arguments) }',
22 | '{ x: (eval = 0) }',
23 | '{ x: (arguments = 0) }',
24 | '{ x: (eval) = 0 }',
25 | '{ x: (arguments) = 0 }',
26 | '[ (eval) ]',
27 | '[ (arguments) ]',
28 | '[ (eval = 0) ]',
29 | '[ (arguments = 0) ]',
30 | '[ (eval) = 0 ]',
31 | '[ (arguments) = 0 ]',
32 | 'arguments',
33 | '(arguments = x);',
34 | '[ ...(eval) ]',
35 | '[ ...(arguments) ]',
36 | '[ ...(eval = 0) ]',
37 | '[ ...(arguments = 0) ]',
38 | '[ ...(eval) = 0 ]',
39 | '[ ...(arguments) = 0 ]'
40 | ]) {
41 | it(`${arg}`, () => {
42 | t.throws(() => {
43 | parseRoot(`o = {foo(${arg}){ "use strict"; }}`, Context.Empty);
44 | });
45 | });
46 | it(`${arg}`, () => {
47 | t.throws(() => {
48 | parseRoot(`'use strict'; (${arg} = {})`, Context.Empty);
49 | });
50 | });
51 | it(`'use strict'; for (${arg} of {}) {}`, () => {
52 | t.throws(() => {
53 | parseRoot(`'use strict'; for (${arg} of {}) {}`, Context.Empty);
54 | });
55 | });
56 | it(`'use strict'; for (${arg} of {}) {}`, () => {
57 | t.throws(() => {
58 | parseRoot(`'use strict'; for (${arg} of {}) {}`, Context.Strict | Context.Module);
59 | });
60 | });
61 | it(`for (${arg} in {}) {}`, () => {
62 | t.throws(() => {
63 | parseRoot(`for (${arg} in {}) {}`, Context.Strict);
64 | });
65 | });
66 | }
67 |
68 | for (const arg of [
69 | 'var eval',
70 | 'var arguments',
71 | 'var foo, eval;',
72 | 'var foo, arguments;',
73 | 'try { } catch (eval) { }',
74 | 'try { } catch (arguments) { }',
75 | 'function eval() { }',
76 | '(eval) => { "use strict"; }',
77 | 'function arguments() { "use strict"; }',
78 | 'function eval() { "use strict"; }',
79 | 'function foo(eval) { }',
80 | 'function foo(arguments) { }',
81 | 'function foo(bar, eval) { }',
82 | 'function foo(bar, arguments) { }',
83 | 'eval => foo',
84 | '(eval) => { }',
85 | '(arguments) => { }',
86 | '(foo, eval) => { }',
87 | '(foo, arguments) => { }',
88 | 'eval = 1;',
89 | 'arguments = 1;',
90 | '[ arguments = 0 ]',
91 | 'var foo = eval = 1;',
92 | 'var foo = arguments = 1;',
93 | '++eval;',
94 | '++arguments;',
95 | 'eval++;',
96 | 'arguments++;',
97 | '--arguments;',
98 | '{ eval = 0 }',
99 | '{ arguments = 0 }',
100 | '[ arguments = 0 ]',
101 | '[ (eval = 0) ]',
102 | '[ (arguments = 0) ]',
103 | '[ (eval) = 0 ]',
104 | '[ (arguments) = 0 ]',
105 | '[ ...(eval) = 0 ]',
106 | '[ ...(arguments) = 0 ]',
107 | '[...eval] = arr',
108 | '(arguments)++;',
109 | '++(arguments);',
110 | '++(eval);',
111 | '(eval)++;',
112 | '(arguments) = 1;',
113 | 'eval++',
114 | '--arguments',
115 | "'use strict'; [eval] = 0",
116 | "'use strict'; [,,,eval,] = 0",
117 | "'use strict'; ({a: eval} = 0)",
118 | "'use strict'; ({a: eval = 0} = 0)",
119 | "'use strict'; [arguments] = 0",
120 | "'use strict'; ({a: arguments = 0} = 0)",
121 | '({arguments}) => null',
122 | '"use strict"; let [eval];',
123 | 'eval=>0',
124 | 'arguments=>0',
125 | '(eval)=>0',
126 | '(arguments)=>0',
127 | "'use strict'; var { x : arguments } = {};",
128 | "'use strict'; var { eval } = {};",
129 | "'use strict'; var { ...arguments } = {};",
130 | "'use strict'; var [arguments] = {};",
131 | "'use strict'; var { eval = false } = {};",
132 | "'use strict'; var { x : arguments } = {};",
133 | "'use strict'; function f(arguments) {}",
134 | "'use strict'; var { x : arguments } = {};",
135 | "'use strict'; function f(argument1, { x : arguments }) {}",
136 | "'use strict'; function f(argument1, { a : arguments }) {}",
137 | "'use strict'; function f(argument1, { x : private }) {}",
138 | '(function arguments(){ "use strict"; })',
139 | '(arguments) => {"use strict";}',
140 | '(x, arguments) => {"use strict";}',
141 | '(x, eval) => {"use strict";}',
142 | 'async (arguments) => {"use strict";}',
143 | 'async (x, arguments) => {"use strict";}',
144 | "'use strict'; function f(argument1, { arguments = false }) {}",
145 | 'function arguments(){}v:switch(x){default:}let arguments=l',
146 | 'arguments[0],'
147 | ]) {
148 | it(`${arg}`, () => {
149 | t.throws(() => {
150 | parseRoot(`${arg}`, Context.OptionsDisableWebCompat | Context.Strict);
151 | });
152 | });
153 |
154 | it(`${arg}`, () => {
155 | t.throws(() => {
156 | parseRoot(`${arg}`, Context.Strict | Context.Module);
157 | });
158 | });
159 | }
160 |
161 | // Valid in strict mode
162 | for (const arg of [
163 | 'eval;',
164 | 'arguments;',
165 | 'eval: a',
166 | 'arguments[1] = 7;',
167 | 'arguments[1]--;',
168 | 'arguments[1] = 7;',
169 | '--arguments[1];',
170 | 'var foo = eval;',
171 | 'var foo = arguments;',
172 | 'var foo = { eval: 1 };',
173 | 'var foo = { arguments: 1 };',
174 | 'var foo = { }; foo.eval = {};',
175 | 'var foo = { }; foo.arguments = {};',
176 | '(0,eval)(true)'
177 | ]) {
178 | it(`"use strict"; ${arg}`, () => {
179 | t.doesNotThrow(() => {
180 | parseRoot(`"use strict"; ${arg}`, Context.Empty);
181 | });
182 | });
183 | it(`() => { "use strict"; ${arg} }`, () => {
184 | t.doesNotThrow(() => {
185 | parseRoot(`() => { "use strict"; ${arg} }`, Context.Empty);
186 | });
187 | });
188 | it(`() => { "use strict"; ${arg} }`, () => {
189 | t.doesNotThrow(() => {
190 | parseRoot(`() => { "use strict"; ${arg} }`, Context.Strict | Context.Module);
191 | });
192 | });
193 | }
194 |
195 | for (const arg of [
196 | 'log(eval)',
197 | 'eval.foo',
198 | 'eval[foo]',
199 | 'eval.foo = bar',
200 | 'eval[foo] = bar',
201 | 'function arguments(a){ }',
202 | `let
203 | arguments`,
204 | 'let arguments',
205 | 'function c(arguments){ }',
206 | 'var eval;',
207 | 'var arguments',
208 | 'var foo, eval;',
209 | 'var foo, arguments;',
210 | 'try { } catch (eval) { }',
211 | 'try { } catch (arguments) { }',
212 | 'function eval() { }',
213 | 'function arguments() { }',
214 | 'function foo(eval) { }',
215 | 'function foo(arguments) { }',
216 | 'function foo(bar, eval) { }',
217 | 'function foo(bar, arguments) { }',
218 | 'eval = 1;',
219 | 'arguments = 1;',
220 | 'var foo = eval = 1;',
221 | 'var foo = arguments = 1;',
222 | '++eval;',
223 | '++arguments;',
224 | 'eval++;',
225 | 'arguments++;',
226 | 'eval;',
227 | 'arguments;',
228 | 'arguments[1] = 7;',
229 | 'arguments[1]--;',
230 | 'foo = arguments[1];',
231 | '({*eval() {}})',
232 | '"use strict"; ({*eval() {}})',
233 | '({*arguments() {}})',
234 | '({get eval() {}})',
235 | '({get arguments() {}})',
236 | '({set eval(_) {}})',
237 | '({set arguments(_) {}})',
238 | '"use strict"; ({set eval(_) {}})',
239 | '"use strict"; ({set arguments(_) {}})',
240 | 'class C {eval() {}}',
241 | 'class C {arguments() {}}',
242 | 'class C {*eval() {}}',
243 | 'class C {*arguments() {}}',
244 | 'class C {get eval() {}}',
245 | 'class C {get arguments() {}}',
246 | 'class C {set eval(_) {}}',
247 | 'class C {set arguments(_) {}}',
248 | 'class C {static eval() {}}',
249 | 'class C {static arguments() {}}',
250 | 'class C {static *eval() {}}',
251 | 'class C {static *arguments() {}}',
252 | 'class C {static get eval() {}}',
253 | 'class C {static get arguments() {}}',
254 | 'class C {static set eval(_) {}}',
255 | 'class C {static set arguments(_) {}}',
256 | 'var actual = "" + function ([arguments]) {return arguments;};',
257 | `(function ([a, b, arguments, d], [e, f, g]) {return arguments();})`,
258 | 'var f = function ([arguments]) {return arguments + 1;};',
259 | 'var o = {"arguments": 42};',
260 | 'delete o.arguments;',
261 | 'var arguments = 42;',
262 | `var equalityCheck = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultEqualityCheck;`,
263 | `function get_arg_2() { return arguments[2]; }`,
264 | 'var n = arguments.length > 1 ? ToInteger(arguments[1]) + 0 : 0;',
265 | 'var lastIndex = arguments.length - 1, lastArg = arguments[lastIndex];',
266 | 'arguments[0]',
267 | 'arguments[0] /** the image src url */',
268 | 'function foo(){writeLine(typeMap[typeof(arguments)]);}foo(1);',
269 | 'function foo(){var arguments;writeLine(typeMap[typeof(arguments)]);}foo(1);',
270 | `eval('arguments="test1"');`,
271 | 'arguments[0] = "arguments[0]";',
272 | 'write("args[1] : " + arguments[1]);',
273 | 'target.apply(that, arguments);',
274 | 'write(x + " " + arguments[0]);',
275 | 'arguments.callee ',
276 | 'arguments.callee.configurable = true',
277 | 'Test4.arguments = 10;',
278 | 'class c { constructor() { result = [...arguments]; } };',
279 | 'eval[0]++;',
280 | '++eval[0];',
281 | 'eval.a++;',
282 | '++eval.a;',
283 | 'arguments[0]++;',
284 | '++arguments[0];',
285 | ' arguments.a++;',
286 | ' ++arguments.a;',
287 | 'delete (((arguments)))',
288 | '( arguments ) = x',
289 | 'for (arguments in x) ;',
290 | '(x=arguments=10) => { }',
291 | 'function e(x=arguments=10){ }',
292 | 'f = function f(x=arguments=10){}',
293 | 'f = { f(x=arguments=10){ }}',
294 | 'delete (arguments).prop',
295 | 'delete (((arguments.prop)))',
296 | 'delete arguments.prop',
297 | 'arguments.length',
298 | 'var x = arguments.length',
299 | '({arguments}) => null',
300 | 'new arguments',
301 | `let t = (arguments)
302 | function* r(n, as, o, s) { "use strict" }`,
303 | 'eval[0] = 1;',
304 | 'arguments.a = 1;',
305 | '++arguments[0];'
306 | ]) {
307 | it(`${arg}`, () => {
308 | t.doesNotThrow(() => {
309 | parseRoot(`${arg}`, Context.Empty);
310 | });
311 | });
312 | it(`function foo() { ${arg} }`, () => {
313 | t.doesNotThrow(() => {
314 | parseRoot(`function foo() { ${arg} }`, Context.Empty);
315 | });
316 | });
317 | }
318 |
--------------------------------------------------------------------------------
/test/parser/miscellaneous/annex-b.ts:
--------------------------------------------------------------------------------
1 | import { parseScript, parseRoot } from '../../../src/seafox';
2 | import { Context } from '../../../src/parser/common';
3 |
4 | import * as t from 'assert';
5 |
6 | describe('Miscellaneous - Annex B', () => {
7 | for (const arg of [
8 | 'x --> is eol-comment\nvar y = 37;\n',
9 | '"\\n" --> is eol-comment\nvar y = 37;\n',
10 | 'x/* precomment */ --> is eol-comment\nvar y = 37;\n',
11 | 'var x = 42; --> is eol-comment\nvar y = 37;\n'
12 | ]) {
13 | it(`${arg}`, () => {
14 | t.throws(() => {
15 | parseRoot(`${arg}`, Context.OptionsDisableWebCompat);
16 | });
17 | });
18 |
19 | it(`${arg}`, () => {
20 | t.throws(() => {
21 | parseRoot(`${arg}`, Context.Strict | Context.Module);
22 | });
23 | });
24 | }
25 |
26 | for (const arg of [
27 | '{ if (x) function f() {} ; function f() {} }',
28 | 'var f = 123; if (true) function f() { } else function _f() {}',
29 | 'if (true) function f() { } else function _f() {}',
30 | ' try { throw null;} catch (f) {if (true) function f() { return 123; } else ; }',
31 | 'for (let f in { key: 0 }) {if (true) function f() { } else ; }',
32 | '{ if (x) function f() {} ; function f() {} }',
33 | 'for (let f of [0]) { if (true) function f() { } else ; }',
34 | '{ function f() {} } if (true) function f() {}',
35 | 'for (let f; ; ) { if (true) function f() { } break; }',
36 | 'if (true) function f() { } else function _f() {}',
37 | 'switch (0) { default: let f; if (false) ; else function f() { } }',
38 | 'switch (0) { default: let f; if (false) ; else function f() { } }',
39 | 'if (false) ; else function f() { }',
40 | 'switch (1) { case 1: function f() { initialBV = f; f = 123; currentBV = f; return "decl"; }}',
41 | 'switch (1) { case 1: function f() {} } function f() {}',
42 | 'try { throw {}; } catch ({ f }) { switch (1) { case 1: function f() { } } }',
43 | 'switch (0) { default: let f; switch (1) { case 1: function f() { } } }',
44 | 'let f = 123; switch (1) { case 1: function f() { } }',
45 | 'for (let f; ; ) { switch (1) { default: function f() { } } break; }',
46 | 'switch (1) { default: function f() { } }',
47 | 'function *f() {} function *f() {}',
48 | '{ function f() {} function f() {} }',
49 | 'if (true) function f() { initialBV = f; f = 123; currentBV = f; return "decl"; } else function _f() {}',
50 | 'if (true) function f() { initialBV = f; f = 123; currentBV = f; return "decl"; } else function _f() {}',
51 | 'switch (0) { default: let f; {function f() { } }}',
52 | '{ let f = 123; { function f() { }} }',
53 | 'try { throw {}; } catch ({ f }) { { function f() { } } }',
54 | '{ function f() { return 1; } { function f() { return 2; } } }',
55 | `/\\}?/u;`,
56 | `/\\{*/u;`,
57 | `/.{.}/;`,
58 | `/[\\w-\\s]/;`,
59 | `/[\\s-\\w]/;`,
60 | `/(?!.){0,}?/;`,
61 | `/{/;`,
62 | `004`,
63 | `076`,
64 | `02`,
65 | 'if (x) function f() { return 23; } else function f() { return 42; }',
66 | 'if (x) function f() {}',
67 | 'x = -1
253 | counter += 1;`,
254 | `/*
255 | */-->the comment extends to these characters`,
256 | `0/*
257 | */-->`,
258 | `0/* optional FirstCommentLine
259 | */-->the comment extends to these characters`,
260 | `0/*
261 | optional
262 | MultiLineCommentChars */-->the comment extends to these characters`,
263 | `0/*
264 | */ /* optional SingleLineDelimitedCommentSequence */-->the comment extends to these characters`,
265 | `0/*
266 | */ /**/ /* second optional SingleLineDelimitedCommentSequence */-->the comment extends to these characters`
267 | ]) {
268 | it(`${arg}`, () => {
269 | t.doesNotThrow(() => {
270 | parseScript(`${arg}`, { next: true, globalReturn: true });
271 | });
272 | });
273 | }
274 | });
275 |
--------------------------------------------------------------------------------
/src/parser/declaration.ts:
--------------------------------------------------------------------------------
1 | import { nextToken } from '../scanner/scan';
2 | import { Token } from '../token';
3 | import { Errors, report } from '../errors';
4 | import * as Types from './types';
5 | import { parseExpressionStatement } from './statements';
6 | import {
7 | ScopeState,
8 | ScopeKind,
9 | addVarName,
10 | addBlockName,
11 | declareUnboundVariable,
12 | createParentScope,
13 | createTopLevelScope
14 | } from './scope';
15 | import {
16 | ParserState,
17 | Context,
18 | BindingKind,
19 | Origin,
20 | expectSemicolon,
21 | setLoc,
22 | validateFunctionName,
23 | isStrictReservedWord,
24 | consumeOpt
25 | } from './common';
26 | import {
27 | parseFunctionLiteral,
28 | parseClassTail,
29 | parseIdentifierFromValue,
30 | parseBindingPatternOrHigher,
31 | parseImportExpression,
32 | parseMemberExpression,
33 | parseAssignmentExpression,
34 | parseImportMetaExpression,
35 | parseExpression
36 | } from './expressions';
37 |
38 | /**
39 | * Parse function declaration
40 | */
41 | export function parseFunctionDeclaration(
42 | parser: ParserState,
43 | context: Context,
44 | scope: ScopeState,
45 | isHoisted: 0 | 1,
46 | isAsync: 0 | 1,
47 | origin: Origin
48 | ): Types.FunctionDeclaration {
49 | return parseHoistableDeclaration(
50 | parser,
51 | context,
52 | scope,
53 | isHoisted,
54 | isAsync,
55 | origin,
56 | parser.start,
57 | parser.line,
58 | parser.column
59 | );
60 | }
61 |
62 | /**
63 | * Parse hoistable declaration
64 | */
65 | export function parseHoistableDeclaration(
66 | parser: ParserState,
67 | context: Context,
68 | scope: ScopeState,
69 | isHoisted: 0 | 1,
70 | isAsync: 0 | 1,
71 | origin: Origin,
72 | start: number,
73 | line: number,
74 | column: number
75 | ): Types.FunctionDeclaration {
76 | nextToken(parser, context, /* allowRegExp */ 1);
77 |
78 | const isGenerator =
79 | (origin & Origin.Statement) !== Origin.Statement
80 | ? consumeOpt(parser, context, Token.Multiply, /* allowRegExp */ 0)
81 | : 0;
82 |
83 | let id: Types.Identifier | null = null;
84 | let firstRestricted: Token | undefined;
85 |
86 | let parent = createTopLevelScope();
87 |
88 | if ((parser.token & 0b00000000001001110000000000000000) > 0) {
89 | const { token, tokenValue, start, line, column } = parser;
90 |
91 | validateFunctionName(parser, context | ((context & 0b0000000000000000000_1100_00000000) << 11), token);
92 |
93 | // In ES6, a function behaves as a lexical binding, except in
94 | // a script scope, or the initial scope of eval or another function.
95 | if ((origin & 0b00000000000000000000000000000100) > 0 && (context & 0b00000000000000000000100000000000) === 0) {
96 | addVarName(parser, context, scope, tokenValue, BindingKind.Variable);
97 | } else {
98 | addBlockName(parser, context, scope, tokenValue, BindingKind.FunctionLexical, origin);
99 | }
100 |
101 | if (isHoisted === 1) declareUnboundVariable(parser, tokenValue);
102 |
103 | parent = createParentScope(parent, ScopeKind.FunctionRoot);
104 |
105 | firstRestricted = token;
106 |
107 | nextToken(parser, context, /* allowRegExp */ 0);
108 |
109 | id = parseIdentifierFromValue(parser, context, tokenValue, start, line, column);
110 | } else if ((origin & Origin.Hoisted) !== Origin.Hoisted) {
111 | report(parser, Errors.DeclNoName, 'Function');
112 | }
113 |
114 | return parseFunctionLiteral(
115 | parser,
116 | ((context | 0b00011101111011100000000100000000) ^ 0b00011001111011100000000100000000) |
117 | ((isAsync * 2 + isGenerator) << 21),
118 | parent,
119 | id,
120 | firstRestricted,
121 | /* isDecl */ 1,
122 | 'FunctionDeclaration',
123 | /* isMethod */ 0,
124 | start,
125 | line,
126 | column
127 | ) as Types.FunctionDeclaration;
128 | }
129 |
130 | /**
131 | * Parse class declaration
132 | */
133 | export function parseClassDeclaration(
134 | parser: ParserState,
135 | context: Context,
136 | scope: ScopeState,
137 | isHoisted: 0 | 1,
138 | isExported: 0 | 1
139 | ): Types.ClassDeclaration {
140 | const { start, line, column } = parser;
141 |
142 | nextToken(parser, context, /* allowRegExp */ 0);
143 |
144 | // Second set of context masks to fix 'super' edge cases
145 | const inheritedContext = (context | Context.InConstructor) ^ Context.InConstructor;
146 |
147 | context |= Context.Strict;
148 |
149 | let id: Types.Identifier | null = null;
150 |
151 | if ((parser.token & 0b00000000001001110000000000000000) > 0 && parser.token !== Token.ExtendsKeyword) {
152 | const { token, start, line, column, tokenValue } = parser;
153 |
154 | if (isStrictReservedWord(parser, context, token, 0)) report(parser, Errors.UnexpectedStrictReserved);
155 |
156 | // A named class creates a new lexical scope with a const binding of the
157 | // class name for the 'inner name'.
158 | addBlockName(parser, context, scope, tokenValue, BindingKind.Class, Origin.None);
159 |
160 | if (isExported === 1) declareUnboundVariable(parser, tokenValue);
161 |
162 | nextToken(parser, context, /* allowRegExp */ 0);
163 |
164 | id = parseIdentifierFromValue(parser, context, tokenValue, start, line, column);
165 | } else if (isHoisted === 0) {
166 | report(parser, Errors.DeclNoName, 'Class');
167 | }
168 |
169 | return parseClassTail(
170 | parser,
171 | context,
172 | inheritedContext,
173 | id,
174 | 0,
175 | 1,
176 | 'ClassDeclaration',
177 | start,
178 | line,
179 | column
180 | ) as Types.ClassDeclaration;
181 | }
182 |
183 | /**
184 | * Parse variable statement or lexical declaration
185 | */
186 | export function parseVariableStatementOrLexicalDeclaration(
187 | parser: ParserState,
188 | context: Context,
189 | scope: ScopeState,
190 | kind: BindingKind,
191 | origin: Origin
192 | ): Types.VariableDeclaration {
193 | const { start, line, column } = parser;
194 |
195 | nextToken(parser, context, /* allowRegExp */ 0);
196 |
197 | const declarations = parseVariableDeclarationListAndDeclarator(parser, context, scope, kind, origin);
198 |
199 | expectSemicolon(parser, context);
200 |
201 | return (context & 0b00000000000000000000000000000010) === 0b00000000000000000000000000000010
202 | ? {
203 | type: 'VariableDeclaration',
204 | kind: kind & BindingKind.Const ? 'const' : kind & BindingKind.Let ? 'let' : 'var',
205 | declarations,
206 | start,
207 | end: parser.endIndex,
208 | loc: setLoc(parser, line, column)
209 | }
210 | : {
211 | type: 'VariableDeclaration',
212 | kind: kind & BindingKind.Const ? 'const' : kind & BindingKind.Let ? 'let' : 'var',
213 | declarations
214 | };
215 | }
216 |
217 | /**
218 | * Parse variable declaration list and variable declarator
219 | */
220 | export function parseVariableDeclarationListAndDeclarator(
221 | parser: ParserState,
222 | context: Context,
223 | scope: ScopeState,
224 | kind: BindingKind,
225 | origin: Origin
226 | ): Types.VariableDeclarator[] {
227 | let id: Types.BindingName;
228 | let init: Types.Expression | null = null;
229 |
230 | const isConstDecl = (kind & BindingKind.Const) === BindingKind.Const;
231 | const list: Types.VariableDeclarator[] = [];
232 |
233 | do {
234 | const { token, start, line, column } = parser;
235 |
236 | id = parseBindingPatternOrHigher(parser, context, scope, kind, origin, token, start, line, column);
237 |
238 | // Note: Always set the 'initializer' to 'null' for each iteration
239 | init = null;
240 |
241 | if (parser.token === Token.Assign) {
242 | nextToken(parser, context, /* allowRegExp */ 1);
243 |
244 | init = parseExpression(parser, context, /* inGroup */ 0);
245 |
246 | // ES6 'const' and binding patterns require initializers
247 | } else if (isConstDecl || (token & Token.IsPatternStart) === Token.IsPatternStart) {
248 | report(parser, Errors.DeclarationMissingInitializer, isConstDecl ? 'const' : 'destructuring');
249 | }
250 |
251 | list.push(
252 | (context & 0b00000000000000000000000000000010) === 0b00000000000000000000000000000010
253 | ? {
254 | type: 'VariableDeclarator',
255 | id,
256 | init,
257 | start,
258 | end: parser.endIndex,
259 | loc: setLoc(parser, line, column)
260 | }
261 | : {
262 | type: 'VariableDeclarator',
263 | id,
264 | init
265 | }
266 | );
267 | } while (consumeOpt(parser, context, Token.Comma, /* allowRegExp */ 1));
268 |
269 | return list;
270 | }
271 |
272 | export function parseDynamicImportStatement(
273 | parser: ParserState,
274 | context: Context,
275 | start: number,
276 | line: number,
277 | column: number
278 | ): Types.ExpressionStatement {
279 | let expr: Types.Expression = parseImportExpression(parser, context, start, line, column);
280 |
281 | /** MemberExpression :
282 | * 1. PrimaryExpression
283 | * 2. MemberExpression [ AssignmentExpression ]
284 | * 3. MemberExpression . IdentifierName
285 | * 4. MemberExpression TemplateLiteral
286 | *
287 | * CallExpression :
288 | * 1. MemberExpression Arguments
289 | * 2. CallExpression ImportCall
290 | * 3. CallExpression Arguments
291 | * 4. CallExpression [ AssignmentExpression ]
292 | * 5. CallExpression . IdentifierName
293 | * 6. CallExpression TemplateLiteral
294 | *
295 | * UpdateExpression ::
296 | * ('++' | '--')? LeftHandSideExpression
297 | *
298 | */
299 |
300 | expr = parseMemberExpression(parser, context, expr, 1, 0, start, line, column);
301 |
302 | /**
303 | * ExpressionStatement[Yield, Await]:
304 | * [lookahead ∉ { {, function, async [no LineTerminator here] function, class, let [ }]Expression[+In, ?Yield, ?Await]
305 | */
306 | return parseExpressionStatement(parser, context, expr, start, line, column);
307 | }
308 |
309 | export function parseImportMetaDeclaration(
310 | parser: ParserState,
311 | context: Context,
312 | start: number,
313 | line: number,
314 | column: number
315 | ): Types.ExpressionStatement {
316 | let expr: Types.Identifier | Types.Expression = parseIdentifierFromValue(
317 | parser,
318 | context,
319 | 'import',
320 | start,
321 | line,
322 | column
323 | );
324 |
325 | expr = parseImportMetaExpression(parser, context, expr, start, line, column);
326 |
327 | /** MemberExpression :
328 | * 1. PrimaryExpression
329 | * 2. MemberExpression [ AssignmentExpression ]
330 | * 3. MemberExpression . IdentifierName
331 | * 4. MemberExpression TemplateLiteral
332 | *
333 | * CallExpression :
334 | * 1. MemberExpression Arguments
335 | * 2. CallExpression ImportCall
336 | * 3. CallExpression Arguments
337 | * 4. CallExpression [ AssignmentExpression ]
338 | * 5. CallExpression . IdentifierName
339 | * 6. CallExpression TemplateLiteral
340 | *
341 | * UpdateExpression ::
342 | * ('++' | '--')? LeftHandSideExpression
343 | */
344 |
345 | expr = parseMemberExpression(parser, context, expr, 1, 0, start, line, column);
346 |
347 | /** AssignmentExpression :
348 | * 1. ConditionalExpression
349 | * 2. LeftHandSideExpression = AssignmentExpression
350 | */
351 |
352 | expr = parseAssignmentExpression(parser, context, 0, 0, expr, start, line, column);
353 |
354 | /**
355 | * ExpressionStatement[Yield, Await]:
356 | * [lookahead ∉ { {, function, async [no LineTerminator here] function, class, let [ }]Expression[+In, ?Yield, ?Await]
357 | */
358 |
359 | return parseExpressionStatement(parser, context, expr, start, line, column);
360 | }
361 |
--------------------------------------------------------------------------------