From 9441d5030211f1c32f5ae8e61d5565cab8bb6823 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 12 Nov 2020 13:01:13 -0800 Subject: [PATCH] feat(typescript-estree): add `parseWithNodeMaps` API (#2760) Ref: https://github.com/prettier/prettier/pull/9636 This allows consumers to reach into the underlying TS AST in cases where our AST doesn't quite solve the use case. Motivating example: We don't (currently) error for code unless TS itself throws an error. TS is _very_ permissive, but that leads to some weird (and invalid) code we don't error for, and don't include in the AST. For example - this is _syntactically_ valid in the TS parser, but they emit a _semantic_ error: ```ts @decorator enum Foo { A = 1 } ``` As it's not a valid place for a decorator - we do not emit its information in the ESTree AST, but as TS treats this as a semantic error - it is parse-time valid. This creates weird states for consumers wherein they cannot see a decorator exists there, which can cause them to ignore it entirely. For linting - this isn't a problem. But for something like prettier - this means that they'll delete the decorator at format time! This is very bad. Until we implement a solution for #1852 - this will allow consumers to bridge the gap themselves. --- packages/typescript-estree/README.md | 49 +- packages/typescript-estree/src/index.ts | 7 +- packages/typescript-estree/src/parser.ts | 31 +- .../lib/__snapshots__/parse.test.ts.snap | 1134 ++++++++--------- .../typescript-estree/tests/lib/parse.test.ts | 48 +- 5 files changed, 684 insertions(+), 585 deletions(-) diff --git a/packages/typescript-estree/README.md b/packages/typescript-estree/README.md index 8a59159647d..b9d40fb1ec0 100644 --- a/packages/typescript-estree/README.md +++ b/packages/typescript-estree/README.md @@ -220,6 +220,18 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { createDefaultProgram?: boolean; } +interface ParserServices { + program: ts.Program; + esTreeNodeToTSNodeMap: WeakMap; + tsNodeToESTreeNodeMap: WeakMap; + hasFullTypeInformation: boolean; +} + +interface ParseAndGenerateServicesResult { + ast: TSESTree.Program; + services: ParserServices; +} + const PARSE_AND_GENERATE_SERVICES_DEFAULT_OPTIONS: ParseOptions = { ...PARSE_DEFAULT_OPTIONS, errorOnTypeScriptSyntacticAndSemanticIssues: false, @@ -233,7 +245,7 @@ const PARSE_AND_GENERATE_SERVICES_DEFAULT_OPTIONS: ParseOptions = { declare function parseAndGenerateServices( code: string, options: ParseOptions = PARSE_DEFAULT_OPTIONS, -): TSESTree.Program; +): ParseAndGenerateServicesResult; ``` Example usage: @@ -242,7 +254,7 @@ Example usage: import { parseAndGenerateServices } from '@typescript-eslint/typescript-estree'; const code = `const hello: string = 'world';`; -const ast = parseAndGenerateServices(code, { +const { ast, services } = parseAndGenerateServices(code, { filePath: '/some/path/to/file/foo.ts', loc: true, project: './tsconfig.json', @@ -250,6 +262,39 @@ const ast = parseAndGenerateServices(code, { }); ``` +#### `parseWithNodeMaps(code, options)` + +Parses the given string of code with the options provided and returns both the ESTree-compatible AST as well as the node maps. +This allows you to work with both ASTs without the overhead of types that may come with `parseAndGenerateServices`. + +```ts +interface ParseWithNodeMapsResult { + ast: TSESTree.Program; + esTreeNodeToTSNodeMap: ParserServices['esTreeNodeToTSNodeMap']; + tsNodeToESTreeNodeMap: ParserServices['tsNodeToESTreeNodeMap']; +} + +declare function parseWithNodeMaps( + code: string, + options: ParseOptions = PARSE_DEFAULT_OPTIONS, +): ParseWithNodeMapsResult; +``` + +Example usage: + +```js +import { parseWithNodeMaps } from '@typescript-eslint/typescript-estree'; + +const code = `const hello: string = 'world';`; +const { ast, esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap } = parseWithNodeMaps( + code, + { + loc: true, + range: true, + }, +); +``` + ### `TSESTree`, `AST_NODE_TYPES` and `AST_TOKEN_TYPES` Types for the AST produced by the parse functions. diff --git a/packages/typescript-estree/src/index.ts b/packages/typescript-estree/src/index.ts index 88af46a2091..a619e48f679 100644 --- a/packages/typescript-estree/src/index.ts +++ b/packages/typescript-estree/src/index.ts @@ -1,9 +1,4 @@ -export { - AST, - parse, - parseAndGenerateServices, - ParseAndGenerateServicesResult, -} from './parser'; +export * from './parser'; export { ParserServices, TSESTreeOptions } from './parser-options'; export { simpleTraverse } from './simple-traverse'; export * from './ts-estree'; diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index d2554bf6195..45dfd18cef7 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -321,11 +321,24 @@ interface ParseAndGenerateServicesResult { ast: AST; services: ParserServices; } +interface ParseWithNodeMapsResult { + ast: AST; + esTreeNodeToTSNodeMap: ParserServices['esTreeNodeToTSNodeMap']; + tsNodeToESTreeNodeMap: ParserServices['tsNodeToESTreeNodeMap']; +} function parse( code: string, options?: T, ): AST { + const { ast } = parseWithNodeMaps(code, options); + return ast; +} + +function parseWithNodeMaps( + code: string, + options?: T, +): ParseWithNodeMapsResult { /** * Reset the parse configuration */ @@ -367,8 +380,13 @@ function parse( /** * Convert the TypeScript AST to an ESTree-compatible one */ - const { estree } = astConverter(ast, extra, false); - return estree as AST; + const { estree, astMaps } = astConverter(ast, extra, false); + + return { + ast: estree as AST, + esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap, + tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap, + }; } function parseAndGenerateServices( @@ -450,4 +468,11 @@ function parseAndGenerateServices( }; } -export { AST, parse, parseAndGenerateServices, ParseAndGenerateServicesResult }; +export { + AST, + parse, + parseAndGenerateServices, + parseWithNodeMaps, + ParseAndGenerateServicesResult, + ParseWithNodeMapsResult, +}; diff --git a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap index 8ea937e0cd9..c1c63f861b6 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap @@ -1,373 +1,62 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`parse() general output should not contain loc 1`] = ` -Object { - "body": Array [ - Object { - "declarations": Array [ - Object { - "id": Object { - "name": "foo", - "range": Array [ - 4, - 7, - ], - "type": "Identifier", - }, - "init": Object { - "name": "bar", - "range": Array [ - 10, - 13, - ], - "type": "Identifier", - }, - "range": Array [ - 4, - 13, - ], - "type": "VariableDeclarator", - }, - ], - "kind": "let", - "range": Array [ - 0, - 14, - ], - "type": "VariableDeclaration", - }, - ], - "range": Array [ - 0, - 14, - ], - "sourceType": "script", - "type": "Program", -} -`; - -exports[`parse() general output should not contain range 1`] = ` -Object { - "body": Array [ - Object { - "declarations": Array [ - Object { - "id": Object { - "loc": Object { - "end": Object { - "column": 7, - "line": 1, - }, - "start": Object { - "column": 4, - "line": 1, - }, - }, - "name": "foo", - "type": "Identifier", - }, - "init": Object { - "loc": Object { - "end": Object { - "column": 13, - "line": 1, - }, - "start": Object { - "column": 10, - "line": 1, - }, - }, - "name": "bar", - "type": "Identifier", - }, - "loc": Object { - "end": Object { - "column": 13, - "line": 1, - }, - "start": Object { - "column": 4, - "line": 1, - }, - }, - "type": "VariableDeclarator", - }, - ], - "kind": "let", - "loc": Object { - "end": Object { - "column": 14, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "type": "VariableDeclaration", - }, - ], - "loc": Object { - "end": Object { - "column": 14, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "sourceType": "script", - "type": "Program", -} -`; - -exports[`parse() general output tokens, comments, locs, and ranges when called with those options 1`] = ` -Object { - "body": Array [ - Object { - "declarations": Array [ - Object { - "id": Object { - "loc": Object { - "end": Object { - "column": 7, - "line": 1, - }, - "start": Object { - "column": 4, - "line": 1, - }, - }, - "name": "foo", - "range": Array [ - 4, - 7, - ], - "type": "Identifier", - }, - "init": Object { - "loc": Object { - "end": Object { - "column": 13, - "line": 1, - }, - "start": Object { - "column": 10, - "line": 1, - }, - }, - "name": "bar", - "range": Array [ - 10, - 13, - ], - "type": "Identifier", - }, - "loc": Object { - "end": Object { - "column": 13, - "line": 1, - }, - "start": Object { - "column": 4, - "line": 1, - }, - }, - "range": Array [ - 4, - 13, - ], - "type": "VariableDeclarator", - }, - ], - "kind": "let", - "loc": Object { - "end": Object { - "column": 14, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "range": Array [ - 0, - 14, - ], - "type": "VariableDeclaration", - }, - ], - "comments": Array [], - "loc": Object { - "end": Object { - "column": 14, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "range": Array [ - 0, - 14, - ], - "sourceType": "script", - "tokens": Array [ - Object { - "loc": Object { - "end": Object { - "column": 3, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "range": Array [ - 0, - 3, - ], - "type": "Keyword", - "value": "let", - }, - Object { - "loc": Object { - "end": Object { - "column": 7, - "line": 1, - }, - "start": Object { - "column": 4, - "line": 1, - }, - }, - "range": Array [ - 4, - 7, - ], - "type": "Identifier", - "value": "foo", - }, - Object { - "loc": Object { - "end": Object { - "column": 9, - "line": 1, - }, - "start": Object { - "column": 8, - "line": 1, - }, - }, - "range": Array [ - 8, - 9, - ], - "type": "Punctuator", - "value": "=", - }, - Object { - "loc": Object { - "end": Object { - "column": 13, - "line": 1, - }, - "start": Object { - "column": 10, - "line": 1, - }, - }, - "range": Array [ - 10, - 13, - ], - "type": "Identifier", - "value": "bar", - }, - Object { - "loc": Object { - "end": Object { - "column": 14, - "line": 1, - }, - "start": Object { - "column": 13, - "line": 1, - }, - }, - "range": Array [ - 13, - 14, - ], - "type": "Punctuator", - "value": ";", - }, - ], - "type": "Program", -} -`; - -exports[`parse() invalid file error messages "parserOptions.extraFileExtensions" is empty the extension does not match 1`] = ` +exports[`parseAndGenerateServices invalid file error messages "parserOptions.extraFileExtensions" is empty the extension does not match 1`] = ` "\\"parserOptions.project\\" has been set for @typescript-eslint/parser. The file does not match your project config: other/unknownFileType.unknown. The extension for the file (.unknown) is non-standard. You should add \\"parserOptions.extraFileExtensions\\" to your config." `; -exports[`parse() invalid file error messages "parserOptions.extraFileExtensions" is non-empty invalid extension 1`] = ` +exports[`parseAndGenerateServices invalid file error messages "parserOptions.extraFileExtensions" is non-empty invalid extension 1`] = ` "\\"parserOptions.project\\" has been set for @typescript-eslint/parser. The file does not match your project config: other/unknownFileType.unknown. Found unexpected extension \\"unknown\\" specified with the \\"extraFileExtensions\\" option. Did you mean \\".unknown\\"? The extension for the file (.unknown) is non-standard. It should be added to your existing \\"parserOptions.extraFileExtensions\\"." `; -exports[`parse() invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension does not match 1`] = ` +exports[`parseAndGenerateServices invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension does not match 1`] = ` "\\"parserOptions.project\\" has been set for @typescript-eslint/parser. The file does not match your project config: other/unknownFileType.unknown. The extension for the file (.unknown) is non-standard. It should be added to your existing \\"parserOptions.extraFileExtensions\\"." `; -exports[`parse() invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension matches duplicate extension 1`] = ` +exports[`parseAndGenerateServices invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension matches duplicate extension 1`] = ` "\\"parserOptions.project\\" has been set for @typescript-eslint/parser. The file does not match your project config: ts/notIncluded.ts. You unnecessarily included the extension \\".ts\\" with the \\"extraFileExtensions\\" option. This extension is already handled by the parser by default. The file must be included in at least one of the projects provided." `; -exports[`parse() invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension matches the file isn't included 1`] = ` +exports[`parseAndGenerateServices invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension matches the file isn't included 1`] = ` "\\"parserOptions.project\\" has been set for @typescript-eslint/parser. The file does not match your project config: other/notIncluded.vue. The file must be included in at least one of the projects provided." `; -exports[`parse() invalid file error messages project includes errors for not included files 1`] = ` +exports[`parseAndGenerateServices invalid file error messages project includes errors for not included files 1`] = ` "\\"parserOptions.project\\" has been set for @typescript-eslint/parser. The file does not match your project config: ts/notIncluded.ts. The file must be included in at least one of the projects provided." `; -exports[`parse() invalid file error messages project includes errors for not included files 2`] = ` +exports[`parseAndGenerateServices invalid file error messages project includes errors for not included files 2`] = ` "\\"parserOptions.project\\" has been set for @typescript-eslint/parser. The file does not match your project config: ts/notIncluded.tsx. The file must be included in at least one of the projects provided." `; -exports[`parse() invalid file error messages project includes errors for not included files 3`] = ` +exports[`parseAndGenerateServices invalid file error messages project includes errors for not included files 3`] = ` "\\"parserOptions.project\\" has been set for @typescript-eslint/parser. The file does not match your project config: js/notIncluded.js. The file must be included in at least one of the projects provided." `; -exports[`parse() invalid file error messages project includes errors for not included files 4`] = ` +exports[`parseAndGenerateServices invalid file error messages project includes errors for not included files 4`] = ` "\\"parserOptions.project\\" has been set for @typescript-eslint/parser. The file does not match your project config: js/notIncluded.jsx. The file must be included in at least one of the projects provided." `; -exports[`parse() isolated parsing should parse .js file - with JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .js file - with JSX content - parserOptions.jsx = false 1`] = ` Object { "ast": Object { "body": Array [ @@ -657,7 +346,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .js file - with JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .js file - with JSX content - parserOptions.jsx = true 1`] = ` Object { "ast": Object { "body": Array [ @@ -947,7 +636,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .js file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .js file - without JSX content - parserOptions.jsx = false 1`] = ` Object { "ast": Object { "body": Array [ @@ -1127,7 +816,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .js file - without JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .js file - without JSX content - parserOptions.jsx = true 1`] = ` Object { "ast": Object { "body": Array [ @@ -1307,7 +996,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .json file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .json file - without JSX content - parserOptions.jsx = false 1`] = ` Object { "ast": Object { "body": Array [ @@ -1526,7 +1215,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .jsx file - with JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with JSX content - parserOptions.jsx = false 1`] = ` Object { "ast": Object { "body": Array [ @@ -1816,7 +1505,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .jsx file - with JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with JSX content - parserOptions.jsx = true 1`] = ` Object { "ast": Object { "body": Array [ @@ -2106,7 +1795,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .jsx file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .jsx file - without JSX content - parserOptions.jsx = false 1`] = ` Object { "ast": Object { "body": Array [ @@ -2286,7 +1975,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .jsx file - without JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .jsx file - without JSX content - parserOptions.jsx = true 1`] = ` Object { "ast": Object { "body": Array [ @@ -2466,7 +2155,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .ts file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .ts file - without JSX content - parserOptions.jsx = false 1`] = ` Object { "ast": Object { "body": Array [ @@ -2646,7 +2335,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .ts file - without JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .ts file - without JSX content - parserOptions.jsx = true 1`] = ` Object { "ast": Object { "body": Array [ @@ -2826,7 +2515,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .tsx file - with JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with JSX content - parserOptions.jsx = false 1`] = ` Object { "ast": Object { "body": Array [ @@ -3116,7 +2805,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .tsx file - with JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with JSX content - parserOptions.jsx = true 1`] = ` Object { "ast": Object { "body": Array [ @@ -3406,7 +3095,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .tsx file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .tsx file - without JSX content - parserOptions.jsx = false 1`] = ` Object { "ast": Object { "body": Array [ @@ -3586,7 +3275,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .tsx file - without JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .tsx file - without JSX content - parserOptions.jsx = true 1`] = ` Object { "ast": Object { "body": Array [ @@ -3766,7 +3455,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .vue file - with JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .vue file - with JSX content - parserOptions.jsx = true 1`] = ` Object { "ast": Object { "body": Array [ @@ -3850,7 +3539,259 @@ Object { }, "loc": Object { "end": Object { - "column": 17, + "column": 17, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 17, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "const", + "loc": Object { + "end": Object { + "column": 18, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 18, + ], + "type": "VariableDeclaration", + }, + ], + "comments": Array [], + "loc": Object { + "end": Object { + "column": 18, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 18, + ], + "sourceType": "script", + "tokens": Array [ + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 5, + ], + "type": "Keyword", + "value": "const", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 7, + ], + "type": "Identifier", + "value": "x", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 1, + }, + "start": Object { + "column": 8, + "line": 1, + }, + }, + "range": Array [ + 8, + 9, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 11, + "line": 1, + }, + "start": Object { + "column": 10, + "line": 1, + }, + }, + "range": Array [ + 10, + 11, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 11, + "line": 1, + }, + }, + "range": Array [ + 11, + 14, + ], + "type": "JSXIdentifier", + "value": "div", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 1, + }, + "start": Object { + "column": 15, + "line": 1, + }, + }, + "range": Array [ + 15, + 16, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 1, + }, + "start": Object { + "column": 16, + "line": 1, + }, + }, + "range": Array [ + 16, + 17, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 18, + "line": 1, + }, + "start": Object { + "column": 17, + "line": 1, + }, + }, + "range": Array [ + 17, + 18, + ], + "type": "Punctuator", + "value": ";", + }, + ], + "type": "Program", + }, + "services": Object { + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, + }, +} +`; + +exports[`parseAndGenerateServices isolated parsing should parse .vue file - without JSX content - parserOptions.jsx = false 1`] = ` +Object { + "ast": Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "name": "x", + "range": Array [ + 6, + 7, + ], + "type": "Identifier", + }, + "init": Object { + "loc": Object { + "end": Object { + "column": 11, + "line": 1, + }, + "start": Object { + "column": 10, + "line": 1, + }, + }, + "range": Array [ + 10, + 11, + ], + "raw": "1", + "type": "Literal", + "value": 1, + }, + "loc": Object { + "end": Object { + "column": 11, "line": 1, }, "start": Object { @@ -3860,7 +3801,7 @@ Object { }, "range": Array [ 6, - 17, + 11, ], "type": "VariableDeclarator", }, @@ -3868,7 +3809,7 @@ Object { "kind": "const", "loc": Object { "end": Object { - "column": 18, + "column": 11, "line": 1, }, "start": Object { @@ -3878,7 +3819,7 @@ Object { }, "range": Array [ 0, - 18, + 11, ], "type": "VariableDeclaration", }, @@ -3886,7 +3827,7 @@ Object { "comments": Array [], "loc": Object { "end": Object { - "column": 18, + "column": 11, "line": 1, }, "start": Object { @@ -3896,7 +3837,7 @@ Object { }, "range": Array [ 0, - 18, + 11, ], "sourceType": "script", "tokens": Array [ @@ -3969,80 +3910,8 @@ Object { 10, 11, ], - "type": "Punctuator", - "value": "<", - }, - Object { - "loc": Object { - "end": Object { - "column": 14, - "line": 1, - }, - "start": Object { - "column": 11, - "line": 1, - }, - }, - "range": Array [ - 11, - 14, - ], - "type": "JSXIdentifier", - "value": "div", - }, - Object { - "loc": Object { - "end": Object { - "column": 16, - "line": 1, - }, - "start": Object { - "column": 15, - "line": 1, - }, - }, - "range": Array [ - 15, - 16, - ], - "type": "Punctuator", - "value": "/", - }, - Object { - "loc": Object { - "end": Object { - "column": 17, - "line": 1, - }, - "start": Object { - "column": 16, - "line": 1, - }, - }, - "range": Array [ - 16, - 17, - ], - "type": "Punctuator", - "value": ">", - }, - Object { - "loc": Object { - "end": Object { - "column": 18, - "line": 1, - }, - "start": Object { - "column": 17, - "line": 1, - }, - }, - "range": Array [ - 17, - 18, - ], - "type": "Punctuator", - "value": ";", + "type": "Numeric", + "value": "1", }, ], "type": "Program", @@ -4056,7 +3925,7 @@ Object { } `; -exports[`parse() isolated parsing should parse .vue file - without JSX content - parserOptions.jsx = false 1`] = ` +exports[`parseAndGenerateServices isolated parsing should parse .vue file - without JSX content - parserOptions.jsx = true 1`] = ` Object { "ast": Object { "body": Array [ @@ -4236,187 +4105,318 @@ Object { } `; -exports[`parse() isolated parsing should parse .vue file - without JSX content - parserOptions.jsx = true 1`] = ` +exports[`parseWithNodeMaps() general output should not contain loc 1`] = ` Object { - "ast": Object { - "body": Array [ - Object { - "declarations": Array [ - Object { - "id": Object { - "loc": Object { - "end": Object { - "column": 7, - "line": 1, - }, - "start": Object { - "column": 6, - "line": 1, - }, + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { + "name": "foo", + "range": Array [ + 4, + 7, + ], + "type": "Identifier", + }, + "init": Object { + "name": "bar", + "range": Array [ + 10, + 13, + ], + "type": "Identifier", + }, + "range": Array [ + 4, + 13, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "let", + "range": Array [ + 0, + 14, + ], + "type": "VariableDeclaration", + }, + ], + "range": Array [ + 0, + 14, + ], + "sourceType": "script", + "type": "Program", +} +`; + +exports[`parseWithNodeMaps() general output should not contain range 1`] = ` +Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 1, + }, + "start": Object { + "column": 4, + "line": 1, }, - "name": "x", - "range": Array [ - 6, - 7, - ], - "type": "Identifier", }, - "init": Object { - "loc": Object { - "end": Object { - "column": 11, - "line": 1, - }, - "start": Object { - "column": 10, - "line": 1, - }, + "name": "foo", + "type": "Identifier", + }, + "init": Object { + "loc": Object { + "end": Object { + "column": 13, + "line": 1, + }, + "start": Object { + "column": 10, + "line": 1, + }, + }, + "name": "bar", + "type": "Identifier", + }, + "loc": Object { + "end": Object { + "column": 13, + "line": 1, + }, + "start": Object { + "column": 4, + "line": 1, + }, + }, + "type": "VariableDeclarator", + }, + ], + "kind": "let", + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "type": "VariableDeclaration", + }, + ], + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "sourceType": "script", + "type": "Program", +} +`; + +exports[`parseWithNodeMaps() general output tokens, comments, locs, and ranges when called with those options 1`] = ` +Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 1, + }, + "start": Object { + "column": 4, + "line": 1, }, - "range": Array [ - 10, - 11, - ], - "raw": "1", - "type": "Literal", - "value": 1, }, + "name": "foo", + "range": Array [ + 4, + 7, + ], + "type": "Identifier", + }, + "init": Object { "loc": Object { "end": Object { - "column": 11, + "column": 13, "line": 1, }, "start": Object { - "column": 6, + "column": 10, "line": 1, }, }, + "name": "bar", "range": Array [ - 6, - 11, + 10, + 13, ], - "type": "VariableDeclarator", - }, - ], - "kind": "const", - "loc": Object { - "end": Object { - "column": 11, - "line": 1, + "type": "Identifier", }, - "start": Object { - "column": 0, - "line": 1, + "loc": Object { + "end": Object { + "column": 13, + "line": 1, + }, + "start": Object { + "column": 4, + "line": 1, + }, }, + "range": Array [ + 4, + 13, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "let", + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, }, - "range": Array [ - 0, - 11, - ], - "type": "VariableDeclaration", - }, - ], - "comments": Array [], - "loc": Object { - "end": Object { - "column": 11, - "line": 1, }, - "start": Object { - "column": 0, - "line": 1, + "range": Array [ + 0, + 14, + ], + "type": "VariableDeclaration", + }, + ], + "comments": Array [], + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 14, + ], + "sourceType": "script", + "tokens": Array [ + Object { + "loc": Object { + "end": Object { + "column": 3, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, }, + "range": Array [ + 0, + 3, + ], + "type": "Keyword", + "value": "let", }, - "range": Array [ - 0, - 11, - ], - "sourceType": "script", - "tokens": Array [ - Object { - "loc": Object { - "end": Object { - "column": 5, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 1, + }, + "start": Object { + "column": 4, + "line": 1, }, - "range": Array [ - 0, - 5, - ], - "type": "Keyword", - "value": "const", }, - Object { - "loc": Object { - "end": Object { - "column": 7, - "line": 1, - }, - "start": Object { - "column": 6, - "line": 1, - }, + "range": Array [ + 4, + 7, + ], + "type": "Identifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 1, + }, + "start": Object { + "column": 8, + "line": 1, }, - "range": Array [ - 6, - 7, - ], - "type": "Identifier", - "value": "x", }, - Object { - "loc": Object { - "end": Object { - "column": 9, - "line": 1, - }, - "start": Object { - "column": 8, - "line": 1, - }, + "range": Array [ + 8, + 9, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 13, + "line": 1, + }, + "start": Object { + "column": 10, + "line": 1, }, - "range": Array [ - 8, - 9, - ], - "type": "Punctuator", - "value": "=", }, - Object { - "loc": Object { - "end": Object { - "column": 11, - "line": 1, - }, - "start": Object { - "column": 10, - "line": 1, - }, + "range": Array [ + 10, + 13, + ], + "type": "Identifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 13, + "line": 1, }, - "range": Array [ - 10, - 11, - ], - "type": "Numeric", - "value": "1", }, - ], - "type": "Program", - }, - "services": Object { - "esTreeNodeToTSNodeMap": WeakMap {}, - "hasFullTypeInformation": false, - "program": Object {}, - "tsNodeToESTreeNodeMap": WeakMap {}, - }, + "range": Array [ + 13, + 14, + ], + "type": "Punctuator", + "value": ";", + }, + ], + "type": "Program", } `; -exports[`parse() non string code should correctly convert code to a string for parse() 1`] = ` +exports[`parseWithNodeMaps() non string code should correctly convert code to a string for parse() 1`] = ` Object { "body": Array [ Object { @@ -4496,7 +4496,7 @@ Object { } `; -exports[`parse() non string code should correctly convert code to a string for parseAndGenerateServices() 1`] = ` +exports[`parseWithNodeMaps() non string code should correctly convert code to a string for parseAndGenerateServices() 1`] = ` Object { "body": Array [ Object { diff --git a/packages/typescript-estree/tests/lib/parse.test.ts b/packages/typescript-estree/tests/lib/parse.test.ts index 6ed6441c4e7..a1c68ce7582 100644 --- a/packages/typescript-estree/tests/lib/parse.test.ts +++ b/packages/typescript-estree/tests/lib/parse.test.ts @@ -8,18 +8,25 @@ import { createSnapshotTestBlock } from '../../tools/test-utils'; const FIXTURES_DIR = join(__dirname, '../fixtures/simpleProject'); -describe('parse()', () => { +describe('parseWithNodeMaps()', () => { describe('basic functionality', () => { it('should parse an empty string', () => { - expect(parser.parse('').body).toEqual([]); - expect(parser.parse('', {}).body).toEqual([]); + expect(parser.parseWithNodeMaps('').ast.body).toEqual([]); + expect(parser.parseWithNodeMaps('', {}).ast.body).toEqual([]); + }); + + it('parse() should be the same as parseWithNodeMaps().ast', () => { + const code = 'const x: number = 1;'; + expect(parser.parseWithNodeMaps(code).ast).toMatchObject( + parser.parse(code), + ); }); }); describe('modules', () => { it('should have correct column number when strict mode error occurs', () => { try { - parser.parse('function fn(a, a) {\n}'); + parser.parseWithNodeMaps('function fn(a, a) {\n}'); } catch (err) { expect(err.column).toEqual(16); } @@ -85,7 +92,7 @@ describe('parse()', () => { const loggerFn = jest.fn(() => {}); - parser.parse('let foo = bar;', { + parser.parseWithNodeMaps('let foo = bar;', { loggerFn, comment: true, tokens: true, @@ -105,7 +112,9 @@ describe('parse()', () => { }); }); }); +}); +describe('parseAndGenerateServices', () => { describe('errorOnTypeScriptSyntacticAndSemanticIssues', () => { const code = '@test const foo = 2'; const options: TSESTreeOptions = { @@ -116,9 +125,9 @@ describe('parse()', () => { errorOnTypeScriptSyntacticAndSemanticIssues: true, }; - it('should throw on invalid option when used in parse', () => { + it('should throw on invalid option when used in parseWithNodeMaps', () => { expect(() => { - parser.parse(code, options); + parser.parseWithNodeMaps(code, options); }).toThrow( `"errorOnTypeScriptSyntacticAndSemanticIssues" is only supported for parseAndGenerateServices()`, ); @@ -176,6 +185,31 @@ describe('parse()', () => { ); }); + it('should not impact the use of parseWithNodeMaps()', () => { + const resultWithNoOptionSet = parser.parseWithNodeMaps(code, baseConfig); + const resultWithOptionSetToTrue = parser.parseWithNodeMaps(code, { + ...baseConfig, + preserveNodeMaps: true, + }); + const resultWithOptionSetToFalse = parser.parseWithNodeMaps(code, { + ...baseConfig, + preserveNodeMaps: false, + }); + const resultWithOptionSetExplicitlyToUndefined = parser.parseWithNodeMaps( + code, + { + ...baseConfig, + preserveNodeMaps: undefined, + }, + ); + + expect(resultWithNoOptionSet).toMatchObject(resultWithOptionSetToTrue); + expect(resultWithNoOptionSet).toMatchObject(resultWithOptionSetToFalse); + expect(resultWithNoOptionSet).toMatchObject( + resultWithOptionSetExplicitlyToUndefined, + ); + }); + it('should preserve node maps by default for parseAndGenerateServices()', () => { const noOptionSet = parser.parseAndGenerateServices(code, baseConfig);