Skip to content

Commit c3061f9

Browse files
authored
feat(ts-estree): add preserveNodeMaps option (#494)
1 parent 6ccf482 commit c3061f9

File tree

7 files changed

+155
-21
lines changed

7 files changed

+155
-21
lines changed

packages/typescript-estree/README.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,19 @@ Parses the given string of code with the options provided and returns an ESTree-
6666
* When value is `false`, no logging will occur.
6767
* When value is not provided, `console.log()` will be used.
6868
*/
69-
loggerFn: undefined
69+
loggerFn: undefined,
70+
71+
/**
72+
* Allows the user to control whether or not two-way AST node maps are preserved
73+
* during the AST conversion process.
74+
*
75+
* By default: the AST node maps are NOT preserved, unless `project` has been specified,
76+
* in which case the maps are made available on the returned `parserServices`.
77+
*
78+
* NOTE: If `preserveNodeMaps` is explicitly set by the user, it will be respected,
79+
* regardless of whether or not `project` is in use.
80+
*/
81+
preserveNodeMaps: undefined
7082
}
7183
```
7284

packages/typescript-estree/src/ast-converter.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Extra } from './parser-options';
77
export default function astConverter(
88
ast: ts.SourceFile,
99
extra: Extra,
10-
shouldProvideParserServices: boolean,
10+
shouldPreserveNodeMaps: boolean,
1111
) {
1212
/**
1313
* The TypeScript compiler produced fundamental parse errors when parsing the
@@ -23,7 +23,7 @@ export default function astConverter(
2323
const instance = new Converter(ast, {
2424
errorOnUnknownASTType: extra.errorOnUnknownASTType || false,
2525
useJSXTextNode: extra.useJSXTextNode || false,
26-
shouldProvideParserServices,
26+
shouldPreserveNodeMaps,
2727
});
2828

2929
const estree = instance.convertProgram();
@@ -42,9 +42,7 @@ export default function astConverter(
4242
estree.comments = convertComments(ast, extra.code);
4343
}
4444

45-
const astMaps = shouldProvideParserServices
46-
? instance.getASTMaps()
47-
: undefined;
45+
const astMaps = shouldPreserveNodeMaps ? instance.getASTMaps() : undefined;
4846

4947
return { estree, astMaps };
5048
}

packages/typescript-estree/src/convert.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const SyntaxKind = ts.SyntaxKind;
2626
interface ConverterOptions {
2727
errorOnUnknownASTType: boolean;
2828
useJSXTextNode: boolean;
29-
shouldProvideParserServices: boolean;
29+
shouldPreserveNodeMaps: boolean;
3030
}
3131

3232
/**
@@ -168,7 +168,7 @@ export class Converter {
168168
node: ts.Node,
169169
result: TSESTree.BaseNode | null,
170170
) {
171-
if (result && this.options.shouldProvideParserServices) {
171+
if (result && this.options.shouldPreserveNodeMaps) {
172172
if (!this.tsNodeToESTreeNodeMap.has(node)) {
173173
this.tsNodeToESTreeNodeMap.set(node, result);
174174
}
@@ -217,7 +217,7 @@ export class Converter {
217217
result.loc = getLocFor(result.range[0], result.range[1], this.ast);
218218
}
219219

220-
if (result && this.options.shouldProvideParserServices) {
220+
if (result && this.options.shouldPreserveNodeMaps) {
221221
this.esTreeNodeToTSNodeMap.set(result, node);
222222
}
223223
return result as T;

packages/typescript-estree/src/parser-options.ts

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface Extra {
1818
projects: string[];
1919
tsconfigRootDir: string;
2020
extraFileExtensions: string[];
21+
preserveNodeMaps?: boolean;
2122
}
2223

2324
export interface ParserOptions {
@@ -34,6 +35,7 @@ export interface ParserOptions {
3435
filePath?: string;
3536
tsconfigRootDir?: string;
3637
extraFileExtensions?: string[];
38+
preserveNodeMaps?: boolean;
3739
}
3840

3941
export interface ParserWeakMap<TKey, TValueBase> {

packages/typescript-estree/src/parser.ts

+24-3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ function resetExtra(): void {
5757
code: '',
5858
tsconfigRootDir: process.cwd(),
5959
extraFileExtensions: [],
60+
preserveNodeMaps: undefined,
6061
};
6162
}
6263

@@ -241,6 +242,18 @@ function applyParserOptionsToExtra(options: ParserOptions): void {
241242
) {
242243
extra.extraFileExtensions = options.extraFileExtensions;
243244
}
245+
/**
246+
* Allow the user to enable or disable the preservation of the AST node maps
247+
* during the conversion process.
248+
*
249+
* NOTE: For backwards compatibility we also preserve node maps in the case where `project` is set,
250+
* and `preserveNodeMaps` is not explicitly set to anything.
251+
*/
252+
extra.preserveNodeMaps =
253+
typeof options.preserveNodeMaps === 'boolean' && options.preserveNodeMaps;
254+
if (options.preserveNodeMaps === undefined && extra.projects.length > 0) {
255+
extra.preserveNodeMaps = true;
256+
}
244257
}
245258

246259
function warnAboutTSVersion(): void {
@@ -372,11 +385,19 @@ export function parseAndGenerateServices<
372385
options,
373386
shouldProvideParserServices,
374387
);
388+
/**
389+
* Determine whether or not two-way maps of converted AST nodes should be preserved
390+
* during the conversion process
391+
*/
392+
const shouldPreserveNodeMaps =
393+
extra.preserveNodeMaps !== undefined
394+
? extra.preserveNodeMaps
395+
: shouldProvideParserServices;
375396
/**
376397
* Convert the TypeScript AST to an ESTree-compatible one, and optionally preserve
377398
* mappings between converted and original AST nodes
378399
*/
379-
const { estree, astMaps } = convert(ast, extra, shouldProvideParserServices);
400+
const { estree, astMaps } = convert(ast, extra, shouldPreserveNodeMaps);
380401
/**
381402
* Even if TypeScript parsed the source code ok, and we had no problems converting the AST,
382403
* there may be other syntactic or semantic issues in the code that we can optionally report on.
@@ -395,11 +416,11 @@ export function parseAndGenerateServices<
395416
services: {
396417
program: shouldProvideParserServices ? program : undefined,
397418
esTreeNodeToTSNodeMap:
398-
shouldProvideParserServices && astMaps
419+
shouldPreserveNodeMaps && astMaps
399420
? astMaps.esTreeNodeToTSNodeMap
400421
: undefined,
401422
tsNodeToESTreeNodeMap:
402-
shouldProvideParserServices && astMaps
423+
shouldPreserveNodeMaps && astMaps
403424
? astMaps.tsNodeToESTreeNodeMap
404425
: undefined,
405426
},

packages/typescript-estree/tests/lib/convert.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('convert', () => {
1818
const instance = new Converter(ast, {
1919
errorOnUnknownASTType: false,
2020
useJSXTextNode: false,
21-
shouldProvideParserServices: false,
21+
shouldPreserveNodeMaps: false,
2222
});
2323
expect(instance.convertProgram()).toMatchSnapshot();
2424
});
@@ -29,7 +29,7 @@ describe('convert', () => {
2929
const instance = new Converter(ast, {
3030
errorOnUnknownASTType: false,
3131
useJSXTextNode: false,
32-
shouldProvideParserServices: false,
32+
shouldPreserveNodeMaps: false,
3333
});
3434
expect((instance as any).deeplyCopy(ast.statements[0])).toMatchSnapshot();
3535
});
@@ -40,7 +40,7 @@ describe('convert', () => {
4040
const instance = new Converter(ast, {
4141
errorOnUnknownASTType: false,
4242
useJSXTextNode: false,
43-
shouldProvideParserServices: false,
43+
shouldPreserveNodeMaps: false,
4444
});
4545
expect((instance as any).deeplyCopy(ast.statements[0])).toMatchSnapshot();
4646
});
@@ -51,7 +51,7 @@ describe('convert', () => {
5151
const instance = new Converter(ast, {
5252
errorOnUnknownASTType: false,
5353
useJSXTextNode: false,
54-
shouldProvideParserServices: false,
54+
shouldPreserveNodeMaps: false,
5555
});
5656
expect(
5757
(instance as any).deeplyCopy((ast.statements[0] as any).expression),
@@ -64,7 +64,7 @@ describe('convert', () => {
6464
const instance = new Converter(ast, {
6565
errorOnUnknownASTType: false,
6666
useJSXTextNode: false,
67-
shouldProvideParserServices: false,
67+
shouldPreserveNodeMaps: false,
6868
});
6969
expect((instance as any).deeplyCopy(ast)).toMatchSnapshot();
7070
});
@@ -75,7 +75,7 @@ describe('convert', () => {
7575
const instance = new Converter(ast, {
7676
errorOnUnknownASTType: true,
7777
useJSXTextNode: false,
78-
shouldProvideParserServices: false,
78+
shouldPreserveNodeMaps: false,
7979
});
8080
expect(() => instance.convertProgram()).toThrow(
8181
'Unknown AST_NODE_TYPE: "TSJSDocNullableType"',
@@ -93,7 +93,7 @@ describe('convert', () => {
9393
const instance = new Converter(ast, {
9494
errorOnUnknownASTType: false,
9595
useJSXTextNode: false,
96-
shouldProvideParserServices: true,
96+
shouldPreserveNodeMaps: true,
9797
});
9898
instance.convertProgram();
9999
const maps = instance.getASTMaps();
@@ -127,7 +127,7 @@ describe('convert', () => {
127127
const instance = new Converter(ast, {
128128
errorOnUnknownASTType: false,
129129
useJSXTextNode: false,
130-
shouldProvideParserServices: true,
130+
shouldPreserveNodeMaps: true,
131131
});
132132
instance.convertProgram();
133133
const maps = instance.getASTMaps();
@@ -160,7 +160,7 @@ describe('convert', () => {
160160
const instance = new Converter(ast, {
161161
errorOnUnknownASTType: false,
162162
useJSXTextNode: false,
163-
shouldProvideParserServices: true,
163+
shouldPreserveNodeMaps: true,
164164
});
165165
const program = instance.convertProgram();
166166
const maps = instance.getASTMaps();

packages/typescript-estree/tests/lib/parse.ts

+101
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ describe('parse()', () => {
8888
tokens: expect.any(Array),
8989
tsconfigRootDir: expect.any(String),
9090
useJSXTextNode: false,
91+
preserveNodeMaps: false,
9192
},
9293
false,
9394
);
@@ -126,4 +127,104 @@ describe('parse()', () => {
126127
}).toThrow('Decorators are not valid here.');
127128
});
128129
});
130+
131+
describe('preserveNodeMaps', () => {
132+
const code = 'var a = true';
133+
const baseConfig: ParserOptions = {
134+
comment: true,
135+
tokens: true,
136+
range: true,
137+
loc: true,
138+
};
139+
140+
it('should not impact the use of parse()', () => {
141+
const resultWithNoOptionSet = parser.parse(code, baseConfig);
142+
const resultWithOptionSetToTrue = parser.parse(code, {
143+
...baseConfig,
144+
preserveNodeMaps: true,
145+
});
146+
const resultWithOptionSetToFalse = parser.parse(code, {
147+
...baseConfig,
148+
preserveNodeMaps: false,
149+
});
150+
const resultWithOptionSetExplicitlyToUndefined = parser.parse(code, {
151+
...baseConfig,
152+
preserveNodeMaps: undefined,
153+
});
154+
155+
expect(resultWithNoOptionSet).toMatchObject(resultWithOptionSetToTrue);
156+
expect(resultWithNoOptionSet).toMatchObject(resultWithOptionSetToFalse);
157+
expect(resultWithNoOptionSet).toMatchObject(
158+
resultWithOptionSetExplicitlyToUndefined,
159+
);
160+
});
161+
162+
it('should not preserve node maps by default for parseAndGenerateServices(), unless `project` is set', () => {
163+
const noOptionSet = parser.parseAndGenerateServices(code, baseConfig);
164+
165+
expect(noOptionSet.services.esTreeNodeToTSNodeMap).toBeUndefined();
166+
expect(noOptionSet.services.tsNodeToESTreeNodeMap).toBeUndefined();
167+
168+
const withProjectNoOptionSet = parser.parseAndGenerateServices(code, {
169+
...baseConfig,
170+
project: './tsconfig.json',
171+
});
172+
173+
expect(withProjectNoOptionSet.services.esTreeNodeToTSNodeMap).toEqual(
174+
expect.any(WeakMap),
175+
);
176+
expect(withProjectNoOptionSet.services.tsNodeToESTreeNodeMap).toEqual(
177+
expect.any(WeakMap),
178+
);
179+
});
180+
181+
it('should preserve node maps for parseAndGenerateServices() when option is `true`, regardless of `project` config', () => {
182+
const optionSetToTrue = parser.parseAndGenerateServices(code, {
183+
...baseConfig,
184+
preserveNodeMaps: true,
185+
});
186+
187+
expect(optionSetToTrue.services.esTreeNodeToTSNodeMap).toEqual(
188+
expect.any(WeakMap),
189+
);
190+
expect(optionSetToTrue.services.tsNodeToESTreeNodeMap).toEqual(
191+
expect.any(WeakMap),
192+
);
193+
194+
const withProjectOptionSetToTrue = parser.parseAndGenerateServices(code, {
195+
...baseConfig,
196+
preserveNodeMaps: true,
197+
project: './tsconfig.json',
198+
});
199+
200+
expect(withProjectOptionSetToTrue.services.esTreeNodeToTSNodeMap).toEqual(
201+
expect.any(WeakMap),
202+
);
203+
expect(withProjectOptionSetToTrue.services.tsNodeToESTreeNodeMap).toEqual(
204+
expect.any(WeakMap),
205+
);
206+
});
207+
208+
it('should not preserve node maps for parseAndGenerateServices() when option is `false`, regardless of `project` config', () => {
209+
const optionSetToFalse = parser.parseAndGenerateServices(code, {
210+
...baseConfig,
211+
preserveNodeMaps: false,
212+
});
213+
214+
expect(optionSetToFalse.services.esTreeNodeToTSNodeMap).toBeUndefined();
215+
expect(optionSetToFalse.services.tsNodeToESTreeNodeMap).toBeUndefined();
216+
217+
const withProjectOptionSetToFalse = parser.parseAndGenerateServices(
218+
code,
219+
{ ...baseConfig, preserveNodeMaps: false, project: './tsconfig.json' },
220+
);
221+
222+
expect(
223+
withProjectOptionSetToFalse.services.esTreeNodeToTSNodeMap,
224+
).toBeUndefined();
225+
expect(
226+
withProjectOptionSetToFalse.services.tsNodeToESTreeNodeMap,
227+
).toBeUndefined();
228+
});
229+
});
129230
});

0 commit comments

Comments
 (0)