Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 01de581

Browse files
committed
feat: always return parserServices, add getParserServices from utils
BREAKING: previously plugins expected that `parserServices` would be `undefined` if full type information wasn't available.
1 parent 66f9741 commit 01de581

File tree

15 files changed

+139
-138
lines changed

15 files changed

+139
-138
lines changed

packages/eslint-plugin-tslint/src/rules/config.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import {
2-
ESLintUtils,
3-
ParserServices,
4-
} from '@typescript-eslint/experimental-utils';
1+
import { ESLintUtils } from '@typescript-eslint/experimental-utils';
52
import memoize from 'lodash.memoize';
63
import { Configuration, RuleSeverity } from 'tslint';
74
import { CustomLinter } from '../custom-linter';
@@ -99,17 +96,7 @@ export default createRule<Options, MessageIds>({
9996
create(context) {
10097
const fileName = context.getFilename();
10198
const sourceCode = context.getSourceCode().text;
102-
const parserServices: ParserServices | undefined = context.parserServices;
103-
104-
/**
105-
* The user needs to have configured "project" in their parserOptions
106-
* for @typescript-eslint/parser
107-
*/
108-
if (!parserServices || !parserServices.program) {
109-
throw new Error(
110-
`You must provide a value for the "parserOptions.project" property for @typescript-eslint/parser`,
111-
);
112-
}
99+
const parserServices = ESLintUtils.getParserServices(context);
113100

114101
/**
115102
* The TSLint rules configuration passed in by the user

packages/eslint-plugin-tslint/tests/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ describe('tslint/error', () => {
148148
linter.defineRule('tslint/config', rule);
149149

150150
expect(() => linter.verify(code, config)).toThrow(
151-
`You must provide a value for the "parserOptions.project" property for @typescript-eslint/parser`,
151+
'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.',
152152
);
153153
}
154154

packages/eslint-plugin/src/util/getParserServices.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

packages/eslint-plugin/src/util/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ import { ESLintUtils } from '@typescript-eslint/experimental-utils';
22

33
export * from './astUtils';
44
export * from './createRule';
5-
export * from './getParserServices';
65
export * from './misc';
76
export * from './types';
87

98
// this is done for convenience - saves migrating all of the old rules
10-
const { applyDefault, deepMerge, isObjectNotArray } = ESLintUtils;
11-
export { applyDefault, deepMerge, isObjectNotArray };
9+
const {
10+
applyDefault,
11+
deepMerge,
12+
isObjectNotArray,
13+
getParserServices,
14+
} = ESLintUtils;
15+
export { applyDefault, deepMerge, isObjectNotArray, getParserServices };

packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,11 @@ function validateTableRules(
104104
// not perfect but should be good enough
105105
const ruleFileContents = fs.readFileSync(
106106
path.resolve(__dirname, `../../src/rules/${ruleName}.ts`),
107+
'utf-8',
108+
);
109+
const usesTypeInformation = /getParserServices\(([^,]+\)|.+?, false\))/.test(
110+
ruleFileContents,
107111
);
108-
const usesTypeInformation = ruleFileContents.includes('getParserServices');
109112
validateTableBoolean(
110113
usesTypeInformation,
111114
rowNeedsTypeInfo,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ParserServices, TSESLint } from '../';
2+
3+
const ERROR_MESSAGE =
4+
'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.';
5+
6+
/**
7+
* Try to retrieve typescript parser service from context
8+
*/
9+
export function getParserServices<
10+
TMessageIds extends string,
11+
TOptions extends readonly any[]
12+
>(
13+
context: TSESLint.RuleContext<TMessageIds, TOptions>,
14+
allowWithoutFullTypeInformation: boolean = false,
15+
): ParserServices {
16+
// backwards compatability check
17+
// old versions of the parser would not return any parserServices unless parserOptions.project was set
18+
if (
19+
!context.parserServices ||
20+
!context.parserServices.program ||
21+
!context.parserServices.esTreeNodeToTSNodeMap ||
22+
!context.parserServices.tsNodeToESTreeNodeMap
23+
) {
24+
throw new Error(ERROR_MESSAGE);
25+
}
26+
27+
const hasFullTypeInformation =
28+
typeof context.parserServices.hasFullTypeInformation === 'boolean'
29+
? context.parserServices.hasFullTypeInformation
30+
: // backwards compatible
31+
true;
32+
33+
// if a rule requries full type information, then hard fail if it doesn't exist
34+
// this forces the user to supply parserOptions.project
35+
if (!hasFullTypeInformation && !allowWithoutFullTypeInformation) {
36+
throw new Error(ERROR_MESSAGE);
37+
}
38+
39+
return context.parserServices;
40+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './applyDefault';
22
export * from './batchedSingleLineTests';
3+
export * from './getParserServices';
34
export * from './RuleCreator';
45
export * from './deepMerge';

packages/experimental-utils/src/ts-eslint/Rule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ interface RuleContext<
169169
/**
170170
* An object containing parser-provided services for rules
171171
*/
172-
parserServices?: ParserServices;
172+
parserServices: ParserServices;
173173

174174
/**
175175
* Returns an array of the ancestors of the currently-traversed node, starting at

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export default function astConverter(
4242
estree.comments = convertComments(ast, extra.code);
4343
}
4444

45-
const astMaps = shouldPreserveNodeMaps ? instance.getASTMaps() : undefined;
45+
const astMaps = instance.getASTMaps();
4646

4747
return { estree, astMaps };
4848
}

packages/typescript-estree/src/convert.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class Converter {
5858
*/
5959
constructor(ast: ts.SourceFile, options: ConverterOptions) {
6060
this.ast = ast;
61-
this.options = options;
61+
this.options = { ...options };
6262
}
6363

6464
getASTMaps() {

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ export interface ParserWeakMap<TKey, TValueBase> {
4545
}
4646

4747
export interface ParserServices {
48-
program: Program | undefined;
49-
esTreeNodeToTSNodeMap: ParserWeakMap<TSESTree.Node, TSNode> | undefined;
50-
tsNodeToESTreeNodeMap: ParserWeakMap<TSNode, TSESTree.Node> | undefined;
48+
hasFullTypeInformation: boolean;
49+
program: Program;
50+
esTreeNodeToTSNodeMap: ParserWeakMap<TSESTree.Node, TSNode>;
51+
tsNodeToESTreeNodeMap: ParserWeakMap<TSNode, TSESTree.Node>;
5152
}

packages/typescript-estree/src/parser.ts

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { TSESTree } from './ts-estree';
99
import {
1010
calculateProjectParserOptions,
1111
createProgram,
12+
unusedVarsOptions,
1213
} from './tsconfig-parser';
1314

1415
/**
@@ -57,7 +58,7 @@ function resetExtra(): void {
5758
code: '',
5859
tsconfigRootDir: process.cwd(),
5960
extraFileExtensions: [],
60-
preserveNodeMaps: undefined,
61+
preserveNodeMaps: true,
6162
};
6263
}
6364

@@ -90,8 +91,11 @@ function getASTFromProject(code: string, options: TSESTreeOptions) {
9091
function getASTAndDefaultProject(code: string, options: TSESTreeOptions) {
9192
const fileName = options.filePath || getFileName(options);
9293
const program = createProgram(code, fileName, extra);
93-
const ast = program && program.getSourceFile(fileName);
94-
return ast && { ast, program };
94+
if (program) {
95+
const ast = program.getSourceFile(fileName);
96+
return ast && { ast, program };
97+
}
98+
return null;
9599
}
96100

97101
/**
@@ -142,6 +146,7 @@ function createNewProgram(code: string) {
142146
noResolve: true,
143147
target: ts.ScriptTarget.Latest,
144148
jsx: extra.jsx ? ts.JsxEmit.Preserve : undefined,
149+
...unusedVarsOptions,
145150
},
146151
compilerHost,
147152
);
@@ -245,13 +250,10 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void {
245250
/**
246251
* Allow the user to enable or disable the preservation of the AST node maps
247252
* 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.
251253
*/
252254
extra.preserveNodeMaps =
253255
typeof options.preserveNodeMaps === 'boolean' && options.preserveNodeMaps;
254-
if (options.preserveNodeMaps === undefined && extra.projects.length > 0) {
256+
if (options.preserveNodeMaps === undefined) {
255257
extra.preserveNodeMaps = true;
256258
}
257259
}
@@ -291,6 +293,7 @@ export interface ParseAndGenerateServicesResult<T extends TSESTreeOptions> {
291293
// Public
292294
//------------------------------------------------------------------------------
293295

296+
// note - cannot migrate this to an import statement because it will make TSC copy the package.json to the dist folder
294297
export const version: string = require('../package.json').version;
295298

296299
export function parse<T extends TSESTreeOptions = TSESTreeOptions>(
@@ -385,19 +388,13 @@ export function parseAndGenerateServices<
385388
options,
386389
shouldProvideParserServices,
387390
);
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;
396391
/**
397392
* Convert the TypeScript AST to an ESTree-compatible one, and optionally preserve
398393
* mappings between converted and original AST nodes
399394
*/
400-
const { estree, astMaps } = convert(ast, extra, shouldPreserveNodeMaps);
395+
const preserveNodeMaps =
396+
typeof extra.preserveNodeMaps === 'boolean' ? extra.preserveNodeMaps : true;
397+
const { estree, astMaps } = convert(ast, extra, preserveNodeMaps);
401398
/**
402399
* Even if TypeScript parsed the source code ok, and we had no problems converting the AST,
403400
* there may be other syntactic or semantic issues in the code that we can optionally report on.
@@ -414,15 +411,10 @@ export function parseAndGenerateServices<
414411
return {
415412
ast: estree as AST<T>,
416413
services: {
417-
program: shouldProvideParserServices ? program : undefined,
418-
esTreeNodeToTSNodeMap:
419-
shouldPreserveNodeMaps && astMaps
420-
? astMaps.esTreeNodeToTSNodeMap
421-
: undefined,
422-
tsNodeToESTreeNodeMap:
423-
shouldPreserveNodeMaps && astMaps
424-
? astMaps.tsNodeToESTreeNodeMap
425-
: undefined,
414+
hasFullTypeInformation: shouldProvideParserServices,
415+
program,
416+
esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap,
417+
tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap,
426418
},
427419
};
428420
}

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ import { Extra } from './parser-options';
66
// Environment calculation
77
//------------------------------------------------------------------------------
88

9-
/**
10-
* Default compiler options for program generation from single root file
11-
*/
12-
const defaultCompilerOptions: ts.CompilerOptions = {
13-
allowNonTsExtensions: true,
14-
allowJs: true,
9+
// these flags are required to make no-unused-vars work
10+
export const unusedVarsOptions = {
11+
noUnusedLocals: true,
12+
noUnusedParameters: true,
1513
};
1614

1715
/**
@@ -99,7 +97,10 @@ export function calculateProjectParserOptions(
9997
// create compiler host
10098
const watchCompilerHost = ts.createWatchCompilerHost(
10199
tsconfigPath,
102-
/*optionsToExtend*/ { allowNonTsExtensions: true } as ts.CompilerOptions,
100+
/*optionsToExtend*/ {
101+
allowNonTsExtensions: true,
102+
...unusedVarsOptions,
103+
} as ts.CompilerOptions,
103104
ts.sys,
104105
ts.createSemanticDiagnosticsBuilderProgram,
105106
diagnosticReporter,
@@ -202,7 +203,11 @@ export function createProgram(code: string, filePath: string, extra: Extra) {
202203

203204
const commandLine = ts.getParsedCommandLineOfConfigFile(
204205
tsconfigPath,
205-
defaultCompilerOptions,
206+
{
207+
allowNonTsExtensions: true,
208+
allowJs: true,
209+
...unusedVarsOptions,
210+
},
206211
{ ...ts.sys, onUnRecoverableConfigFileDiagnostic: () => {} },
207212
);
208213

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ describe('convert', () => {
9898
instance.convertProgram();
9999
const maps = instance.getASTMaps();
100100

101-
function checkMaps(child: any) {
102-
child.forEachChild((node: any) => {
101+
function checkMaps(child: ts.SourceFile | ts.Node) {
102+
child.forEachChild(node => {
103103
if (
104104
node.kind !== ts.SyntaxKind.EndOfFileToken &&
105105
node.kind !== ts.SyntaxKind.JsxAttributes &&
@@ -132,8 +132,8 @@ describe('convert', () => {
132132
instance.convertProgram();
133133
const maps = instance.getASTMaps();
134134

135-
function checkMaps(child: any) {
136-
child.forEachChild((node: any) => {
135+
function checkMaps(child: ts.SourceFile | ts.Node) {
136+
child.forEachChild(node => {
137137
if (
138138
node.kind !== ts.SyntaxKind.EndOfFileToken &&
139139
node.kind !== ts.SyntaxKind.JsxAttributes
@@ -165,8 +165,8 @@ describe('convert', () => {
165165
const program = instance.convertProgram();
166166
const maps = instance.getASTMaps();
167167

168-
function checkMaps(child: any) {
169-
child.forEachChild((node: any) => {
168+
function checkMaps(child: ts.SourceFile | ts.Node) {
169+
child.forEachChild(node => {
170170
if (node.kind !== ts.SyntaxKind.EndOfFileToken) {
171171
expect(ast).toBe(
172172
maps.esTreeNodeToTSNodeMap.get(maps.tsNodeToESTreeNodeMap.get(ast)),

0 commit comments

Comments
 (0)