├── .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 | Seafox NPM 9 | GitHub license 10 | Total alerts 11 | Circle 12 | License 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 | --------------------------------------------------------------------------------