Skip to content

Commit 9441d50

Browse files
authored
feat(typescript-estree): add parseWithNodeMaps API (#2760)
Ref: prettier/prettier#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.
1 parent 535db3b commit 9441d50

File tree

5 files changed

+684
-585
lines changed

5 files changed

+684
-585
lines changed

packages/typescript-estree/README.md

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,18 @@ interface ParseAndGenerateServicesOptions extends ParseOptions {
220220
createDefaultProgram?: boolean;
221221
}
222222

223+
interface ParserServices {
224+
program: ts.Program;
225+
esTreeNodeToTSNodeMap: WeakMap<TSESTree.Node, ts.Node | ts.Token>;
226+
tsNodeToESTreeNodeMap: WeakMap<ts.Node | ts.Token, TSESTree.Node>;
227+
hasFullTypeInformation: boolean;
228+
}
229+
230+
interface ParseAndGenerateServicesResult<T extends TSESTreeOptions> {
231+
ast: TSESTree.Program;
232+
services: ParserServices;
233+
}
234+
223235
const PARSE_AND_GENERATE_SERVICES_DEFAULT_OPTIONS: ParseOptions = {
224236
...PARSE_DEFAULT_OPTIONS,
225237
errorOnTypeScriptSyntacticAndSemanticIssues: false,
@@ -233,7 +245,7 @@ const PARSE_AND_GENERATE_SERVICES_DEFAULT_OPTIONS: ParseOptions = {
233245
declare function parseAndGenerateServices(
234246
code: string,
235247
options: ParseOptions = PARSE_DEFAULT_OPTIONS,
236-
): TSESTree.Program;
248+
): ParseAndGenerateServicesResult;
237249
```
238250

239251
Example usage:
@@ -242,14 +254,47 @@ Example usage:
242254
import { parseAndGenerateServices } from '@typescript-eslint/typescript-estree';
243255

244256
const code = `const hello: string = 'world';`;
245-
const ast = parseAndGenerateServices(code, {
257+
const { ast, services } = parseAndGenerateServices(code, {
246258
filePath: '/some/path/to/file/foo.ts',
247259
loc: true,
248260
project: './tsconfig.json',
249261
range: true,
250262
});
251263
```
252264

265+
#### `parseWithNodeMaps(code, options)`
266+
267+
Parses the given string of code with the options provided and returns both the ESTree-compatible AST as well as the node maps.
268+
This allows you to work with both ASTs without the overhead of types that may come with `parseAndGenerateServices`.
269+
270+
```ts
271+
interface ParseWithNodeMapsResult<T extends TSESTreeOptions> {
272+
ast: TSESTree.Program;
273+
esTreeNodeToTSNodeMap: ParserServices['esTreeNodeToTSNodeMap'];
274+
tsNodeToESTreeNodeMap: ParserServices['tsNodeToESTreeNodeMap'];
275+
}
276+
277+
declare function parseWithNodeMaps(
278+
code: string,
279+
options: ParseOptions = PARSE_DEFAULT_OPTIONS,
280+
): ParseWithNodeMapsResult;
281+
```
282+
283+
Example usage:
284+
285+
```js
286+
import { parseWithNodeMaps } from '@typescript-eslint/typescript-estree';
287+
288+
const code = `const hello: string = 'world';`;
289+
const { ast, esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap } = parseWithNodeMaps(
290+
code,
291+
{
292+
loc: true,
293+
range: true,
294+
},
295+
);
296+
```
297+
253298
### `TSESTree`, `AST_NODE_TYPES` and `AST_TOKEN_TYPES`
254299

255300
Types for the AST produced by the parse functions.

packages/typescript-estree/src/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
export {
2-
AST,
3-
parse,
4-
parseAndGenerateServices,
5-
ParseAndGenerateServicesResult,
6-
} from './parser';
1+
export * from './parser';
72
export { ParserServices, TSESTreeOptions } from './parser-options';
83
export { simpleTraverse } from './simple-traverse';
94
export * from './ts-estree';

packages/typescript-estree/src/parser.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,11 +321,24 @@ interface ParseAndGenerateServicesResult<T extends TSESTreeOptions> {
321321
ast: AST<T>;
322322
services: ParserServices;
323323
}
324+
interface ParseWithNodeMapsResult<T extends TSESTreeOptions> {
325+
ast: AST<T>;
326+
esTreeNodeToTSNodeMap: ParserServices['esTreeNodeToTSNodeMap'];
327+
tsNodeToESTreeNodeMap: ParserServices['tsNodeToESTreeNodeMap'];
328+
}
324329

325330
function parse<T extends TSESTreeOptions = TSESTreeOptions>(
326331
code: string,
327332
options?: T,
328333
): AST<T> {
334+
const { ast } = parseWithNodeMaps(code, options);
335+
return ast;
336+
}
337+
338+
function parseWithNodeMaps<T extends TSESTreeOptions = TSESTreeOptions>(
339+
code: string,
340+
options?: T,
341+
): ParseWithNodeMapsResult<T> {
329342
/**
330343
* Reset the parse configuration
331344
*/
@@ -367,8 +380,13 @@ function parse<T extends TSESTreeOptions = TSESTreeOptions>(
367380
/**
368381
* Convert the TypeScript AST to an ESTree-compatible one
369382
*/
370-
const { estree } = astConverter(ast, extra, false);
371-
return estree as AST<T>;
383+
const { estree, astMaps } = astConverter(ast, extra, false);
384+
385+
return {
386+
ast: estree as AST<T>,
387+
esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap,
388+
tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap,
389+
};
372390
}
373391

374392
function parseAndGenerateServices<T extends TSESTreeOptions = TSESTreeOptions>(
@@ -450,4 +468,11 @@ function parseAndGenerateServices<T extends TSESTreeOptions = TSESTreeOptions>(
450468
};
451469
}
452470

453-
export { AST, parse, parseAndGenerateServices, ParseAndGenerateServicesResult };
471+
export {
472+
AST,
473+
parse,
474+
parseAndGenerateServices,
475+
parseWithNodeMaps,
476+
ParseAndGenerateServicesResult,
477+
ParseWithNodeMapsResult,
478+
};

0 commit comments

Comments
 (0)