Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(typescript-estree): always return parserServices #716

Merged
merged 16 commits into from May 10, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -68,7 +68,7 @@ export default util.createRule<Options, MessageIds>({
},
],
create(context, [userOptions]) {
const parserServices = util.getParserServices(context);
const parserServices = util.getParserServices(context, true);
armano2 marked this conversation as resolved.
Show resolved Hide resolved
const tsProgram = parserServices.program;
const afterAllDiagnosticsCallbacks: (() => void)[] = [];

Expand Down Expand Up @@ -189,7 +189,7 @@ export default util.createRule<Options, MessageIds>({
parent: ts.ParameterDeclaration,
): void {
const name = identifier.getText();
// regardless of if the paramter is ignored, track that it had a diagnostic fired on it
// regardless of if the parameter is ignored, track that it had a diagnostic fired on it
unusedParameters.add(identifier);

/*
Expand Down
Expand Up @@ -14,8 +14,6 @@ const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
project: './tsconfig.json',
tsconfigRootDir: rootDir,
},
parser: '@typescript-eslint/parser',
});
Expand Down
31 changes: 19 additions & 12 deletions packages/experimental-utils/src/eslint-utils/getParserServices.ts
Expand Up @@ -3,29 +3,36 @@ import { ParserServices, TSESLint } from '../';
const ERROR_MESSAGE =
'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.';

type RequiredParserServices = {
[k in keyof ParserServices]: Exclude<ParserServices[k], undefined>;
};

/**
* Try to retrieve typescript parser service from context
*/
export function getParserServices<
TMessageIds extends string,
TOptions extends unknown[]
TOptions extends readonly unknown[]
>(
context: TSESLint.RuleContext<TMessageIds, TOptions>,
): RequiredParserServices {
allowWithoutFullTypeInformation = false,
): ParserServices {
// backwards compatibility check
// old versions of the parser would not return any parserServices unless parserOptions.project was set
if (
!context.parserServices ||
!context.parserServices.program ||
!context.parserServices.esTreeNodeToTSNodeMap
!context.parserServices.esTreeNodeToTSNodeMap ||
!context.parserServices.tsNodeToESTreeNodeMap
) {
/**
* The user needs to have configured "project" in their parserOptions
* for @typescript-eslint/parser
*/
throw new Error(ERROR_MESSAGE);
}
return context.parserServices as RequiredParserServices;

const hasFullTypeInformation =
context.parserServices.hasFullTypeInformation ??
/* backwards compatible */ true;

// if a rule requires full type information, then hard fail if it doesn't exist
// this forces the user to supply parserOptions.project
if (!hasFullTypeInformation && !allowWithoutFullTypeInformation) {
throw new Error(ERROR_MESSAGE);
}

return context.parserServices;
}
4 changes: 2 additions & 2 deletions packages/typescript-estree/src/ast-converter.ts
Expand Up @@ -10,7 +10,7 @@ export function astConverter(
ast: SourceFile,
extra: Extra,
shouldPreserveNodeMaps: boolean,
): { estree: TSESTree.Program; astMaps: ASTMaps | undefined } {
): { estree: TSESTree.Program; astMaps: ASTMaps } {
/**
* The TypeScript compiler produced fundamental parse errors when parsing the
* source.
Expand Down Expand Up @@ -63,7 +63,7 @@ export function astConverter(
estree.comments = convertComments(ast, extra.code);
}

const astMaps = shouldPreserveNodeMaps ? instance.getASTMaps() : undefined;
const astMaps = instance.getASTMaps();

return { estree, astMaps };
}
Expand Up @@ -78,7 +78,6 @@ function createProjectProgram(
errorLines.push(
'The file must be included in at least one of the projects provided.',
);
hasMatchedAnError = true;
}

throw new Error(errorLines.join('\n'));
Expand Down
5 changes: 4 additions & 1 deletion packages/typescript-estree/src/create-program/shared.ts
Expand Up @@ -4,7 +4,7 @@ import { Extra } from '../parser-options';

interface ASTAndProgram {
ast: ts.SourceFile;
program: ts.Program | undefined;
program: ts.Program;
}

/**
Expand All @@ -16,6 +16,9 @@ const DEFAULT_COMPILER_OPTIONS: ts.CompilerOptions = {
checkJs: true,
noEmit: true,
// extendedDiagnostics: true,
/**
* Flags required to make no-unused-vars work
*/
noUnusedLocals: true,
noUnusedParameters: true,
};
Expand Down
9 changes: 4 additions & 5 deletions packages/typescript-estree/src/parser-options.ts
Expand Up @@ -167,9 +167,8 @@ export interface ParserWeakMapESTreeToTSNode<
}

export interface ParserServices {
program: Program | undefined;
esTreeNodeToTSNodeMap: ParserWeakMapESTreeToTSNode | undefined;
tsNodeToESTreeNodeMap:
| ParserWeakMap<TSNode | TSToken, TSESTree.Node>
| undefined;
program: Program;
esTreeNodeToTSNodeMap: ParserWeakMapESTreeToTSNode;
tsNodeToESTreeNodeMap: ParserWeakMap<TSNode | TSToken, TSESTree.Node>;
hasFullTypeInformation: boolean;
}
48 changes: 13 additions & 35 deletions packages/typescript-estree/src/parser.ts
Expand Up @@ -12,7 +12,7 @@ import { createSourceFile } from './create-program/createSourceFile';
import { Extra, TSESTreeOptions, ParserServices } from './parser-options';
import { getFirstSemanticOrSyntacticError } from './semantic-or-syntactic-errors';
import { TSESTree } from './ts-estree';
import { ensureAbsolutePath } from './create-program/shared';
import { ASTAndProgram, ensureAbsolutePath } from './create-program/shared';

/**
* This needs to be kept in sync with the top-level README.md in the
Expand Down Expand Up @@ -46,11 +46,6 @@ function enforceString(code: unknown): string {
return code;
}

interface ASTAndProgram {
ast: ts.SourceFile;
program: ts.Program | undefined;
}

/**
* @param code The code of the file being linted
* @param shouldProvideParserServices True if the program should be attempted to be calculated from provided tsconfig files
Expand All @@ -61,7 +56,7 @@ function getProgramAndAST(
code: string,
shouldProvideParserServices: boolean,
shouldCreateDefaultProgram: boolean,
): ASTAndProgram | undefined {
): ASTAndProgram {
return (
(shouldProvideParserServices &&
createProjectProgram(code, shouldCreateDefaultProgram, extra)) ||
Expand Down Expand Up @@ -101,7 +96,7 @@ function resetExtra(): void {
jsx: false,
loc: false,
log: console.log, // eslint-disable-line no-console
preserveNodeMaps: undefined,
preserveNodeMaps: true,
projects: [],
range: false,
strict: false,
Expand Down Expand Up @@ -166,7 +161,7 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void {
}

/**
* Get the file extension
* Get the file path
*/
if (typeof options.filePath === 'string' && options.filePath !== '<input>') {
extra.filePath = options.filePath;
Expand Down Expand Up @@ -244,14 +239,9 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void {
/**
* Allow the user to enable or disable the preservation of the AST node maps
* during the conversion process.
*
* NOTE: For backwards compatibility we also preserve node maps in the case where `project` is set,
* and `preserveNodeMaps` is not explicitly set to anything.
*/
extra.preserveNodeMaps =
typeof options.preserveNodeMaps === 'boolean' && options.preserveNodeMaps;
if (options.preserveNodeMaps === undefined && extra.projects.length > 0) {
extra.preserveNodeMaps = true;
if (typeof options.preserveNodeMaps === 'boolean') {
extra.preserveNodeMaps = options.preserveNodeMaps;
}

extra.createDefaultProgram =
Expand Down Expand Up @@ -393,20 +383,13 @@ function parseAndGenerateServices<T extends TSESTreeOptions = TSESTreeOptions>(
extra.createDefaultProgram,
)!;

/**
* Determine if two-way maps of converted AST nodes should be preserved
* during the conversion process
*/
const shouldPreserveNodeMaps =
extra.preserveNodeMaps !== undefined
? extra.preserveNodeMaps
: shouldProvideParserServices;

/**
* Convert the TypeScript AST to an ESTree-compatible one, and optionally preserve
* mappings between converted and original AST nodes
*/
const { estree, astMaps } = astConverter(ast, extra, shouldPreserveNodeMaps);
const preserveNodeMaps =
typeof extra.preserveNodeMaps === 'boolean' ? extra.preserveNodeMaps : true;
const { estree, astMaps } = astConverter(ast, extra, preserveNodeMaps);

/**
* Even if TypeScript parsed the source code ok, and we had no problems converting the AST,
Expand All @@ -425,15 +408,10 @@ function parseAndGenerateServices<T extends TSESTreeOptions = TSESTreeOptions>(
return {
ast: estree as AST<T>,
services: {
program: shouldProvideParserServices ? program : undefined,
esTreeNodeToTSNodeMap:
shouldPreserveNodeMaps && astMaps
? astMaps.esTreeNodeToTSNodeMap
: undefined,
tsNodeToESTreeNodeMap:
shouldPreserveNodeMaps && astMaps
? astMaps.tsNodeToESTreeNodeMap
: undefined,
hasFullTypeInformation: shouldProvideParserServices,
program,
esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap,
tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap,
},
};
}
Expand Down