From 1918024e86acdf315dbbe659af6fea5d7244b336 Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Mon, 14 Oct 2019 11:19:36 -0700 Subject: [PATCH 01/11] chore: declare TypeScript as an optional peerDependency (#990) --- packages/eslint-plugin/package.json | 5 +++++ packages/typescript-estree/package.json | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index c9e1b0fe0da5..6633b8f2ca59 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -58,5 +58,10 @@ "peerDependencies": { "@typescript-eslint/parser": "^2.0.0", "eslint": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } } diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 4716b2ba19d6..7b0a0ed58fd8 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -62,5 +62,10 @@ "lodash.isplainobject": "4.0.6", "tmp": "^0.1.0", "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } } From 5f093ac50ad5efb48d20b2e4a77c585ef8b4787f Mon Sep 17 00:00:00 2001 From: Hailey Date: Tue, 15 Oct 2019 18:07:55 -0700 Subject: [PATCH 02/11] feat(eslint-plugin): Support abstract members in member-ordering rule (#395) (#1004) --- .../docs/rules/member-ordering.md | 28 +++- .../src/rules/member-ordering.ts | 31 +++- packages/eslint-plugin/src/util/misc.ts | 5 +- .../tests/rules/member-ordering.test.ts | 144 +++++++++++++++++- 4 files changed, 195 insertions(+), 13 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/member-ordering.md b/packages/eslint-plugin/docs/rules/member-ordering.md index 50c4a7eacd33..517673f09d68 100644 --- a/packages/eslint-plugin/docs/rules/member-ordering.md +++ b/packages/eslint-plugin/docs/rules/member-ordering.md @@ -37,6 +37,9 @@ There are multiple ways to specify the member types. The most explicit and granu 'public-instance-field', 'protected-instance-field', 'private-instance-field', + 'public-abstract-field', + 'protected-abstract-field', + 'private-abstract-field', // Constructors 'public-constructor', @@ -50,6 +53,9 @@ There are multiple ways to specify the member types. The most explicit and granu 'public-instance-method', 'protected-instance-method', 'private-instance-method', + 'public-abstract-method', + 'protected-abstract-method', + 'private-abstract-method', ] ``` @@ -57,7 +63,7 @@ Note: If you only specify some of the possible types, the non-specified ones can ### Member group types (with accessibility, ignoring scope) -It is also possible to group member types by their accessibility (`static`, `instance`), ignoring their scope. +It is also possible to group member types by their accessibility (`static`, `instance`, `abstract`), ignoring their scope. ```json5 [ @@ -85,13 +91,15 @@ Another option is to group the member types by their scope (`public`, `protected // Fields 'static-field', // = ['public-static-field', 'protected-static-field', 'private-static-field']) 'instance-field', // = ['public-instance-field', 'protected-instance-field', 'private-instance-field']) + 'abstract-field', // = ['public-abstract-field', 'protected-abstract-field', 'private-abstract-field']) // Constructors 'constructor', // = ['public-constructor', 'protected-constructor', 'private-constructor']) // Methods 'static-method', // = ['public-static-method', 'protected-static-method', 'private-static-method']) - 'instance-method', // = ['public-instance-method', 'protected-instance-method', 'private-instance-method'] + 'instance-method', // = ['public-instance-method', 'protected-instance-method', 'private-instance-method']) + 'abstract-method', // = ['public-abstract-method', 'protected-abstract-method', 'private-abstract-method']) ] ``` @@ -102,13 +110,15 @@ The third grouping option is to ignore both scope and accessibility. ```json5 [ // Fields - 'field', // = ['public-static-field', 'protected-static-field', 'private-static-field', 'public-instance-field', 'protected-instance-field', 'private-instance-field']) + 'field', // = ['public-static-field', 'protected-static-field', 'private-static-field', 'public-instance-field', 'protected-instance-field', 'private-instance-field', + // 'public-abstract-field', 'protected-abstract-field', private-abstract-field']) // Constructors // Only the accessibility of constructors is configurable. See above. // Methods - 'method', // = ['public-static-method', 'protected-static-method', 'private-static-method', 'public-instance-method', 'protected-instance-method', 'private-instance-method']) + 'method', // = ['public-static-method', 'protected-static-method', 'private-static-method', 'public-instance-method', 'protected-instance-method', 'private-instance-method', + // 'public-abstract-method', 'protected-abstract-method', 'private-abstract-method']) ] ``` @@ -127,12 +137,17 @@ The default configuration looks as follows: "protected-instance-field", "private-instance-field", + "public-abstract-field", + "protected-abstract-field", + "private-abstract-field", + "public-field", "protected-field", "private-field", "static-field", "instance-field", + "abstract-field", "field", @@ -146,12 +161,17 @@ The default configuration looks as follows: "protected-instance-method", "private-instance-method", + "public-abstract-method", + "protected-abstract-method", + "private-abstract-method", + "public-method", "protected-method", "private-method", "static-method", "instance-method", + "abstract-method", "method" ] diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index 72020afc6008..b17076856949 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -24,8 +24,8 @@ const allMemberTypes = ['field', 'method', 'constructor'].reduce( all.push(`${accessibility}-${type}`); // e.g. `public-field` if (type !== 'constructor') { - // There is no `static-constructor` or `instance-constructor - ['static', 'instance'].forEach(scope => { + // There is no `static-constructor` or `instance-constructor or `abstract-constructor` + ['static', 'instance', 'abstract'].forEach(scope => { if (!all.includes(`${scope}-${type}`)) { all.push(`${scope}-${type}`); } @@ -138,12 +138,17 @@ export default util.createRule({ 'protected-instance-field', 'private-instance-field', + 'public-abstract-field', + 'protected-abstract-field', + 'private-abstract-field', + 'public-field', 'protected-field', 'private-field', 'static-field', 'instance-field', + 'abstract-field', 'field', @@ -157,12 +162,17 @@ export default util.createRule({ 'protected-instance-method', 'private-instance-method', + 'public-abstract-method', + 'protected-abstract-method', + 'private-abstract-method', + 'public-method', 'protected-method', 'private-method', 'static-method', 'instance-method', + 'abstract-method', 'method', ], @@ -185,15 +195,15 @@ export default util.createRule({ ): string | null { // TODO: add missing TSCallSignatureDeclaration // TODO: add missing TSIndexSignature - // TODO: add missing TSAbstractClassProperty - // TODO: add missing TSAbstractMethodDefinition switch (node.type) { + case AST_NODE_TYPES.TSAbstractMethodDefinition: case AST_NODE_TYPES.MethodDefinition: return node.kind; case AST_NODE_TYPES.TSMethodSignature: return 'method'; case AST_NODE_TYPES.TSConstructSignatureDeclaration: return 'constructor'; + case AST_NODE_TYPES.TSAbstractClassProperty: case AST_NODE_TYPES.ClassProperty: return node.value && functionExpressions.includes(node.value.type) ? 'method' @@ -215,8 +225,10 @@ export default util.createRule({ switch (node.type) { case AST_NODE_TYPES.TSPropertySignature: case AST_NODE_TYPES.TSMethodSignature: + case AST_NODE_TYPES.TSAbstractClassProperty: case AST_NODE_TYPES.ClassProperty: return util.getNameFromPropertyName(node.key); + case AST_NODE_TYPES.TSAbstractMethodDefinition: case AST_NODE_TYPES.MethodDefinition: return node.kind === 'constructor' ? 'constructor' @@ -268,7 +280,16 @@ export default util.createRule({ return order.length - 1; } - const scope = 'static' in node && node.static ? 'static' : 'instance'; + const abstract = + node.type === 'TSAbstractClassProperty' || + node.type === 'TSAbstractMethodDefinition'; + + const scope = + 'static' in node && node.static + ? 'static' + : abstract + ? 'abstract' + : 'instance'; const accessibility = 'accessibility' in node && node.accessibility ? node.accessibility diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index cae887c229f1..58c41249d473 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -96,7 +96,10 @@ export function findFirstResult( * or ClassProperty node, with handling for computed property names. */ export function getNameFromClassMember( - methodDefinition: TSESTree.MethodDefinition | TSESTree.ClassProperty, + methodDefinition: + | TSESTree.MethodDefinition + | TSESTree.ClassProperty + | TSESTree.TSAbstractMethodDefinition, sourceCode: TSESLint.SourceCode, ): string { if (keyCanBeReadAsPropertyName(methodDefinition.key)) { diff --git a/packages/eslint-plugin/tests/rules/member-ordering.test.ts b/packages/eslint-plugin/tests/rules/member-ordering.test.ts index 81e67999b8dd..936fe3a89004 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering.test.ts @@ -1211,12 +1211,45 @@ type Foo = { `, options: [{ default: ['method', 'constructor', 'field'] }], }, - ` + { + code: ` abstract class Foo { B: string; abstract A: () => {} } `, + }, + { + code: ` +interface Foo { + public B: string; + [A:string]: number; +} + `, + }, + { + code: ` +abstract class Foo { + private static C: string; + B: string; + private D: string; + protected static F(): {}; + public E(): {}; + public abstract A = () => {}; + protected abstract G(): void; +} + `, + }, + { + code: ` +abstract class Foo { + protected typeChecker: (data: any) => boolean; + public abstract required: boolean; + abstract verify(): void; +} + `, + options: [{ classes: ['field', 'constructor', 'method'] }], + }, ], invalid: [ { @@ -3319,7 +3352,7 @@ type Foo = { { code: ` abstract class Foo { - abstract A: () => {} + abstract A = () => {}; B: string; } `, @@ -3328,12 +3361,117 @@ abstract class Foo { messageId: 'incorrectOrder', data: { name: 'B', - rank: 'method', + rank: 'public abstract method', }, line: 4, column: 5, }, ], }, + { + code: ` +abstract class Foo { + abstract A: () => {}; + B: string; + public C() {}; + private D() {}; + abstract E() {}; +} + `, + errors: [ + { + messageId: 'incorrectOrder', + data: { + name: 'B', + rank: 'public abstract field', + }, + line: 4, + column: 5, + }, + ], + }, + { + code: ` +abstract class Foo { + B: string; + abstract C = () => {}; + abstract A: () => {}; +} + `, + options: [{ default: ['method', 'constructor', 'field'] }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + name: 'C', + rank: 'field', + }, + line: 4, + column: 5, + }, + ], + }, + { + code: ` +class Foo { + C: number; + [A:string]: number; + public static D(): {}; + private static [B:string]: number; +} + `, + options: [ + { + default: [ + 'field', + 'method', + 'public-static-method', + 'private-static-method', + ], + }, + ], + errors: [ + { + messageId: 'incorrectOrder', + data: { + name: 'D', + rank: 'private static method', + }, + line: 5, + column: 5, + }, + ], + }, + { + code: ` +abstract class Foo { + abstract B: string; + abstract A(): void; + public C(): {}; + +} + `, + options: [{ default: ['method', 'constructor', 'field'] }], + errors: [ + { + messageId: 'incorrectOrder', + data: { + name: 'A', + rank: 'field', + }, + line: 4, + column: 5, + }, + { + messageId: 'incorrectOrder', + data: { + name: 'C', + rank: 'field', + }, + line: 5, + column: 5, + }, + ], + }, ], }); From ec627477fb314e6e268d9d5ddd384902af4cebed Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Tue, 15 Oct 2019 18:12:17 -0700 Subject: [PATCH 03/11] fix(typescript-estree): handle running out of fs watchers (#1088) --- packages/typescript-estree/README.md | 5 + packages/typescript-estree/package.json | 2 + packages/typescript-estree/src/parser.ts | 50 ++++++-- .../typescript-estree/src/tsconfig-parser.ts | 119 ++++++++++++------ yarn.lock | 11 +- 5 files changed, 137 insertions(+), 50 deletions(-) diff --git a/packages/typescript-estree/README.md b/packages/typescript-estree/README.md index 55cf645d88ec..fe1515c869ee 100644 --- a/packages/typescript-estree/README.md +++ b/packages/typescript-estree/README.md @@ -144,6 +144,11 @@ I work closely with the TypeScript Team and we are gradually aliging the AST of - `npm run unit-tests` - run only unit tests - `npm run ast-alignment-tests` - run only Babylon AST alignment tests +## Debugging + +If you encounter a bug with the parser that you want to investigate, you can turn on the debug logging via setting the environment variable: `DEBUG=typescript-eslint:*`. +I.e. in this repo you can run: `DEBUG=typescript-eslint:* yarn lint`. + ## License TypeScript ESTree inherits from the the original TypeScript ESLint Parser license, as the majority of the work began there. It is licensed under a permissive BSD 2-clause license. diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 7b0a0ed58fd8..225380a2bd27 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "chokidar": "^3.0.2", + "debug": "^4.1.1", "glob": "^7.1.4", "is-glob": "^4.0.1", "lodash.unescape": "4.0.1", @@ -50,6 +51,7 @@ "@babel/parser": "7.5.5", "@babel/types": "^7.3.2", "@types/babel-code-frame": "^6.20.1", + "@types/debug": "^4.1.5", "@types/glob": "^7.1.1", "@types/is-glob": "^4.0.1", "@types/lodash.isplainobject": "^4.0.4", diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 07435780a37d..6aec2452e5a0 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -1,3 +1,4 @@ +import debug from 'debug'; import path from 'path'; import semver from 'semver'; import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports @@ -15,6 +16,8 @@ import { defaultCompilerOptions, } from './tsconfig-parser'; +const log = debug('typescript-eslint:typescript-estree:parser'); + /** * This needs to be kept in sync with the top-level README.md in the * typescript-eslint monorepo @@ -41,6 +44,17 @@ function getFileName({ jsx }: { jsx?: boolean }): string { return jsx ? 'estree.tsx' : 'estree.ts'; } +function enforceString(code: unknown): string { + /** + * Ensure the source code is a string + */ + if (typeof code !== 'string') { + return String(code); + } + + return code; +} + /** * Resets the extra config object */ @@ -82,6 +96,8 @@ function getASTFromProject( options: TSESTreeOptions, createDefaultProgram: boolean, ): ASTAndProgram | undefined { + log('Attempting to get AST from project(s) for: %s', options.filePath); + const filePath = options.filePath || getFileName(options); const astAndProgram = firstDefined( calculateProjectParserOptions(code, filePath, extra), @@ -139,6 +155,11 @@ function getASTAndDefaultProject( code: string, options: TSESTreeOptions, ): ASTAndProgram | undefined { + log( + 'Attempting to get AST from the default project(s): %s', + options.filePath, + ); + const fileName = options.filePath || getFileName(options); const program = createProgram(code, fileName, extra); const ast = program && program.getSourceFile(fileName); @@ -150,6 +171,8 @@ function getASTAndDefaultProject( * @returns Returns a new source file and program corresponding to the linted code */ function createNewProgram(code: string): ASTAndProgram { + log('Getting AST without type information'); + const FILENAME = getFileName(extra); const compilerHost: ts.CompilerHost = { @@ -226,6 +249,9 @@ function getProgramAndAST( } function applyParserOptionsToExtra(options: TSESTreeOptions): void { + /** + * Turn on/off filesystem watchers + */ extra.noWatch = typeof options.noWatch === 'boolean' && options.noWatch; /** @@ -378,6 +404,7 @@ export function parse( * Reset the parse configuration */ resetExtra(); + /** * Ensure users do not attempt to use parse() when they need parseAndGenerateServices() */ @@ -386,24 +413,25 @@ export function parse( `"errorOnTypeScriptSyntacticAndSemanticIssues" is only supported for parseAndGenerateServices()`, ); } + /** * Ensure the source code is a string, and store a reference to it */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (typeof code !== 'string' && !((code as any) instanceof String)) { - code = String(code); - } + code = enforceString(code); extra.code = code; + /** * Apply the given parser options */ if (typeof options !== 'undefined') { applyParserOptionsToExtra(options); } + /** * Warn if the user is using an unsupported version of TypeScript */ warnAboutTSVersion(); + /** * Create a ts.SourceFile directly, no ts.Program is needed for a simple * parse @@ -414,6 +442,7 @@ export function parse( ts.ScriptTarget.Latest, /* setParentNodes */ true, ); + /** * Convert the TypeScript AST to an ESTree-compatible one */ @@ -428,14 +457,13 @@ export function parseAndGenerateServices< * Reset the parse configuration */ resetExtra(); + /** * Ensure the source code is a string, and store a reference to it */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (typeof code !== 'string' && !((code as any) instanceof String)) { - code = String(code); - } + code = enforceString(code); extra.code = code; + /** * Apply the given parser options */ @@ -449,10 +477,12 @@ export function parseAndGenerateServices< extra.errorOnTypeScriptSyntacticAndSemanticIssues = true; } } + /** * Warn if the user is using an unsupported version of TypeScript */ warnAboutTSVersion(); + /** * Generate a full ts.Program in order to be able to provide parser * services, such as type-checking @@ -465,6 +495,7 @@ export function parseAndGenerateServices< shouldProvideParserServices, extra.createDefaultProgram, )!; + /** * Determine whether or not two-way maps of converted AST nodes should be preserved * during the conversion process @@ -473,11 +504,13 @@ export function parseAndGenerateServices< 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); + /** * Even if TypeScript parsed the source code ok, and we had no problems converting the AST, * there may be other syntactic or semantic issues in the code that we can optionally report on. @@ -488,6 +521,7 @@ export function parseAndGenerateServices< throw convertError(error); } } + /** * Return the converted AST and additional parser services */ diff --git a/packages/typescript-estree/src/tsconfig-parser.ts b/packages/typescript-estree/src/tsconfig-parser.ts index 4dab9030a55b..5f3b97c036ea 100644 --- a/packages/typescript-estree/src/tsconfig-parser.ts +++ b/packages/typescript-estree/src/tsconfig-parser.ts @@ -1,12 +1,11 @@ import chokidar from 'chokidar'; +import debug from 'debug'; import path from 'path'; import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import { Extra } from './parser-options'; import { WatchCompilerHostOfConfigFile } from './WatchCompilerHostOfConfigFile'; -//------------------------------------------------------------------------------ -// Environment calculation -//------------------------------------------------------------------------------ +const log = debug('typescript-eslint:typescript-estree:tsconfig-parser'); /** * Default compiler options for program generation from single root file @@ -33,16 +32,18 @@ const knownWatchProgramMap = new Map< */ const watchCallbackTrackingMap = new Map>(); -/** - * Tracks the ts.sys.watchFile watchers that we've opened for config files. - * We store these so we can clean up our handles if required. - */ -const configSystemFileWatcherTrackingSet = new Set(); +interface Watcher { + close(): void; + forceClose(): void; + on(evt: 'add', listener: (file: string) => void): void; + on(evt: 'change', listener: (file: string) => void): void; + trackWatcher(): void; +} /** * Tracks the ts.sys.watchDirectory watchers that we've opened for project folders. * We store these so we can clean up our handles if required. */ -const directorySystemFileWatcherTrackingSet = new Set(); +const fileWatcherTrackingSet = new Map(); const parsedFilesSeen = new Set(); @@ -56,12 +57,8 @@ export function clearCaches(): void { parsedFilesSeen.clear(); // stop tracking config files - configSystemFileWatcherTrackingSet.forEach(cb => cb.close()); - configSystemFileWatcherTrackingSet.clear(); - - // stop tracking folders - directorySystemFileWatcherTrackingSet.forEach(cb => cb.close()); - directorySystemFileWatcherTrackingSet.clear(); + fileWatcherTrackingSet.forEach(cb => cb.forceClose()); + fileWatcherTrackingSet.clear(); } /** @@ -88,34 +85,84 @@ function getTsconfigPath(tsconfigPath: string, extra: Extra): string { : path.join(extra.tsconfigRootDir || process.cwd(), tsconfigPath); } -interface Watcher { - close(): void; - on(evt: 'add', listener: (file: string) => void): void; - on(evt: 'change', listener: (file: string) => void): void; -} +const EMPTY_WATCHER: Watcher = { + close: (): void => {}, + forceClose: (): void => {}, + on: (): void => {}, + trackWatcher: (): void => {}, +}; + /** * Watches a file or directory for changes */ function watch( - path: string, + watchPath: string, options: chokidar.WatchOptions, extra: Extra, ): Watcher { // an escape hatch to disable the file watchers as they can take a bit to initialise in some cases // this also supports an env variable so it's easy to switch on/off from the CLI - if (process.env.PARSER_NO_WATCH === 'true' || extra.noWatch === true) { - return { - close: (): void => {}, - on: (): void => {}, - }; + const blockWatchers = + process.env.PARSER_NO_WATCH === 'false' + ? false + : process.env.PARSER_NO_WATCH === 'true' || extra.noWatch === true; + if (blockWatchers) { + return EMPTY_WATCHER; + } + + // reuse watchers in case typescript asks us to watch the same file/directory multiple times + if (fileWatcherTrackingSet.has(watchPath)) { + const watcher = fileWatcherTrackingSet.get(watchPath)!; + watcher.trackWatcher(); + return watcher; } - return chokidar.watch(path, { - ignoreInitial: true, - persistent: false, - useFsEvents: false, - ...options, - }); + let fsWatcher: chokidar.FSWatcher; + try { + log('setting up watcher on path: %s', watchPath); + fsWatcher = chokidar.watch(watchPath, { + ignoreInitial: true, + persistent: false, + useFsEvents: false, + ...options, + }); + } catch (e) { + log( + 'error occurred using file watcher, setting up polling watcher instead: %s', + watchPath, + ); + // https://github.com/microsoft/TypeScript/blob/c9d407b52ad92370cd116105c33d618195de8070/src/compiler/sys.ts#L1232-L1237 + // Catch the exception and use polling instead + // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point + // so instead of throwing error, use fs.watchFile + fsWatcher = chokidar.watch(watchPath, { + ignoreInitial: true, + persistent: false, + useFsEvents: false, + ...options, + usePolling: true, + }); + } + + let counter = 1; + const watcher = { + close: (): void => { + counter -= 1; + if (counter <= 0) { + fsWatcher.close(); + fileWatcherTrackingSet.delete(watchPath); + } + }, + forceClose: fsWatcher.close.bind(fsWatcher), + on: fsWatcher.on.bind(fsWatcher), + trackWatcher: (): void => { + counter += 1; + }, + }; + + fileWatcherTrackingSet.set(watchPath, watcher); + + return watcher; } /** @@ -219,7 +266,6 @@ export function calculateProjectParserOptions( watcher.on('change', path => { callback(path, ts.FileWatcherEventKind.Changed); }); - configSystemFileWatcherTrackingSet.add(watcher); } const normalizedFileName = path.normalize(fileName); @@ -239,7 +285,6 @@ export function calculateProjectParserOptions( if (watcher) { watcher.close(); - configSystemFileWatcherTrackingSet.delete(watcher); } }, }; @@ -263,13 +308,9 @@ export function calculateProjectParserOptions( watcher.on('add', path => { callback(path); }); - directorySystemFileWatcherTrackingSet.add(watcher); return { - close(): void { - watcher.close(); - directorySystemFileWatcherTrackingSet.delete(watcher); - }, + close: watcher.close, }; }; diff --git a/yarn.lock b/yarn.lock index 3a524d7a1cbe..1817d623b819 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1329,6 +1329,11 @@ dependencies: "@babel/types" "^7.3.0" +"@types/debug@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" + integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -7758,9 +7763,9 @@ typedarray@^0.0.6: integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= typescript@*, "typescript@>=3.2.1 <3.8.0 >3.7.0-dev.0", typescript@^3.7.0-beta: - version "3.7.0-dev.20191006" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191006.tgz#b455c0bdbc29625b6f95886e17c85ded1271f588" - integrity sha512-0uxLQ41QwguSZdMlQ5GUXljS42Ti1+AFJ1EnTsQOSX4Z0eG2bwxHDJItIRDGV6yZGBMXJ6HGapxv2qxSeW5svA== + version "3.7.0-dev.20191015" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191015.tgz#283a99aeb09c91963aa16adcf5cb2fccbea9bdc4" + integrity sha512-Cpfj1n4pEUVKL+jtS0mkZodJffyMmf3Wk/UjyZMGX4fsjK5KBPJf3NUlyXij8I8p1E2CAomdS5NPFrAR+z8pKw== uglify-js@^3.1.4: version "3.6.0" From 16adda4199509477f9e08c487d4bc29f00dd7f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Oddsson?= Date: Wed, 16 Oct 2019 16:50:16 +0100 Subject: [PATCH 04/11] chore(typescript-estree): fix package name in warning message (#1093) --- packages/typescript-estree/src/parser.ts | 2 +- packages/typescript-estree/tests/lib/warn-on-unsupported-ts.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 6aec2452e5a0..7efde256c81d 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -364,7 +364,7 @@ function warnAboutTSVersion(): void { const border = '============='; const versionWarning = [ border, - 'WARNING: You are currently running a version of TypeScript which is not officially supported by typescript-estree.', + 'WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.', 'You may find that it works just fine, or you may not.', `SUPPORTED TYPESCRIPT VERSIONS: ${SUPPORTED_TYPESCRIPT_VERSIONS}`, `YOUR TYPESCRIPT VERSION: ${ACTIVE_TYPESCRIPT_VERSION}`, diff --git a/packages/typescript-estree/tests/lib/warn-on-unsupported-ts.ts b/packages/typescript-estree/tests/lib/warn-on-unsupported-ts.ts index 97493a5915ae..9e6a26a39a66 100644 --- a/packages/typescript-estree/tests/lib/warn-on-unsupported-ts.ts +++ b/packages/typescript-estree/tests/lib/warn-on-unsupported-ts.ts @@ -15,7 +15,7 @@ describe('Warn on unsupported TypeScript version', () => { parser.parse(''); expect(console.log).toHaveBeenCalledWith( expect.stringContaining( - 'WARNING: You are currently running a version of TypeScript which is not officially supported by typescript-estree', + 'WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree', ), ); }); From 0c85ac3dadf59e475317bbbe422447c08689b36b Mon Sep 17 00:00:00 2001 From: Alexander T Date: Wed, 16 Oct 2019 22:28:56 +0300 Subject: [PATCH 05/11] fix(eslint-plugin): [no-magic-numbers] Support negative numbers (#1072) Co-authored-by: Brad Zacher --- .../src/rules/no-magic-numbers.ts | 55 +++++++++++++++---- .../tests/rules/no-magic-numbers.test.ts | 14 +++++ 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-magic-numbers.ts b/packages/eslint-plugin/src/rules/no-magic-numbers.ts index 01a2505498f6..e3597cf83485 100644 --- a/packages/eslint-plugin/src/rules/no-magic-numbers.ts +++ b/packages/eslint-plugin/src/rules/no-magic-numbers.ts @@ -160,11 +160,23 @@ export default util.createRule({ * @private */ function isParentTSReadonlyClassProperty(node: TSESTree.Node): boolean { - return ( - !!node.parent && + if ( + node.parent && + node.parent.type === AST_NODE_TYPES.UnaryExpression && + ['-', '+'].includes(node.parent.operator) + ) { + node = node.parent; + } + + if ( + node.parent && node.parent.type === AST_NODE_TYPES.ClassProperty && - !!node.parent.readonly - ); + node.parent.readonly + ) { + return true; + } + + return false; } return { @@ -174,13 +186,6 @@ export default util.createRule({ return; } - if ( - options.ignoreReadonlyClassProperties && - isParentTSReadonlyClassProperty(node) - ) { - return; - } - // Check TypeScript specific nodes for Numeric Literal if ( options.ignoreNumericLiteralTypes && @@ -190,6 +195,34 @@ export default util.createRule({ return; } + // Check if the node is a readonly class property + if (isNumber(node) && isParentTSReadonlyClassProperty(node)) { + if (options.ignoreReadonlyClassProperties) { + return; + } + + let fullNumberNode: + | TSESTree.Literal + | TSESTree.UnaryExpression = node; + let raw = node.raw; + + if ( + node.parent && + node.parent.type === AST_NODE_TYPES.UnaryExpression + ) { + fullNumberNode = node.parent; + raw = `${node.parent.operator}${node.raw}`; + } + + context.report({ + messageId: 'noMagic', + node: fullNumberNode, + data: { raw }, + }); + + return; + } + // Let the base rule deal with the rest rules.Literal(node); }, diff --git a/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts b/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts index 1ab26cfa704d..f65610ea3f4b 100644 --- a/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts +++ b/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts @@ -48,6 +48,8 @@ class Foo { readonly B = 2; public static readonly C = 1; static readonly D = 1; + readonly E = -1; + readonly F = +1; } `, options: [{ ignoreReadonlyClassProperties: true }], @@ -184,6 +186,8 @@ class Foo { readonly B = 2; public static readonly C = 1; static readonly D = 1; + readonly E = -1; + readonly F = +1; } `, options: [{ ignoreReadonlyClassProperties: false }], @@ -208,6 +212,16 @@ class Foo { line: 6, column: 23, }, + { + messageId: 'noMagic', + line: 7, + column: 16, + }, + { + messageId: 'noMagic', + line: 8, + column: 16, + }, ], }, ], From ed5564d22ca198c98048e93f1beacec715c427b5 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sat, 19 Oct 2019 11:54:34 -0700 Subject: [PATCH 06/11] feat(typescript-estree): support long running lint without watch (#1106) --- .vscode/launch.json | 16 + packages/eslint-plugin/tests/RuleTester.ts | 43 +- .../create-program/createDefaultProgram.ts | 59 +++ .../create-program/createIsolatedProgram.ts | 71 +++ .../create-program/createProjectProgram.ts | 72 +++ .../src/create-program/createSourceFile.ts | 18 + .../src/create-program/createWatchProgram.ts | 440 ++++++++++++++++++ .../src/create-program/shared.ts | 27 ++ .../typescript-estree/src/parser-options.ts | 3 +- packages/typescript-estree/src/parser.ts | 291 ++++-------- .../typescript-estree/src/tsconfig-parser.ts | 391 ---------------- .../fixtures/invalidFileErrors/tsconfig.json | 3 + packages/typescript-estree/tests/lib/parse.ts | 38 +- .../tests/lib/persistentParse.ts | 106 +++-- .../tests/lib/semanticInfo.ts | 4 +- 15 files changed, 882 insertions(+), 700 deletions(-) create mode 100644 packages/typescript-estree/src/create-program/createDefaultProgram.ts create mode 100644 packages/typescript-estree/src/create-program/createIsolatedProgram.ts create mode 100644 packages/typescript-estree/src/create-program/createProjectProgram.ts create mode 100644 packages/typescript-estree/src/create-program/createSourceFile.ts create mode 100644 packages/typescript-estree/src/create-program/createWatchProgram.ts create mode 100644 packages/typescript-estree/src/create-program/shared.ts delete mode 100644 packages/typescript-estree/src/tsconfig-parser.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 474d245895c0..6552aae66136 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -35,6 +35,22 @@ "sourceMaps": true, "console": "integratedTerminal", "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "Run currently opened parser test", + "cwd": "${workspaceFolder}/packages/parser/", + "program": "${workspaceFolder}/node_modules/jest/bin/jest.js", + "args": [ + "--runInBand", + "--no-cache", + "--no-coverage", + "${relativeFile}" + ], + "sourceMaps": true, + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" } ] } diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index f96d1739f182..7d8bdb2c6955 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -8,20 +8,30 @@ type RuleTesterConfig = Omit & { parser: typeof parser; }; class RuleTester extends TSESLint.RuleTester { - private filename: string | undefined = undefined; - // as of eslint 6 you have to provide an absolute path to the parser // but that's not as clean to type, this saves us trying to manually enforce // that contributors require.resolve everything - constructor(options: RuleTesterConfig) { + constructor(private readonly options: RuleTesterConfig) { super({ ...options, parser: require.resolve(options.parser), }); + } + private getFilename(options?: TSESLint.ParserOptions): string { + if (options) { + const filename = `file.ts${ + options.ecmaFeatures && options.ecmaFeatures.jsx ? 'x' : '' + }`; + if (options.project) { + return path.join(getFixturesRootDir(), filename); + } - if (options.parserOptions && options.parserOptions.project) { - this.filename = path.join(getFixturesRootDir(), 'file.ts'); + return filename; + } else if (this.options.parserOptions) { + return this.getFilename(this.options.parserOptions); } + + return 'file.ts'; } // as of eslint 6 you have to provide an absolute path to the parser @@ -34,17 +44,14 @@ class RuleTester extends TSESLint.RuleTester { ): void { const errorMessage = `Do not set the parser at the test level unless you want to use a parser other than ${parser}`; - if (this.filename) { - tests.valid = tests.valid.map(test => { - if (typeof test === 'string') { - return { - code: test, - filename: this.filename, - }; - } - return test; - }); - } + tests.valid = tests.valid.map(test => { + if (typeof test === 'string') { + return { + code: test, + }; + } + return test; + }); tests.valid.forEach(test => { if (typeof test !== 'string') { @@ -52,7 +59,7 @@ class RuleTester extends TSESLint.RuleTester { throw new Error(errorMessage); } if (!test.filename) { - test.filename = this.filename; + test.filename = this.getFilename(test.parserOptions); } } }); @@ -61,7 +68,7 @@ class RuleTester extends TSESLint.RuleTester { throw new Error(errorMessage); } if (!test.filename) { - test.filename = this.filename; + test.filename = this.getFilename(test.parserOptions); } }); diff --git a/packages/typescript-estree/src/create-program/createDefaultProgram.ts b/packages/typescript-estree/src/create-program/createDefaultProgram.ts new file mode 100644 index 000000000000..e124bd8ed573 --- /dev/null +++ b/packages/typescript-estree/src/create-program/createDefaultProgram.ts @@ -0,0 +1,59 @@ +import debug from 'debug'; +import path from 'path'; +import ts from 'typescript'; +import { Extra } from '../parser-options'; +import { + getTsconfigPath, + DEFAULT_COMPILER_OPTIONS, + ASTAndProgram, +} from './shared'; + +const log = debug('typescript-eslint:typescript-estree:createDefaultProgram'); + +/** + * @param code The code of the file being linted + * @param options The config object + * @param extra.tsconfigRootDir The root directory for relative tsconfig paths + * @param extra.projects Provided tsconfig paths + * @returns If found, returns the source file corresponding to the code and the containing program + */ +function createDefaultProgram( + code: string, + extra: Extra, +): ASTAndProgram | undefined { + log('Getting default program for: %s', extra.filePath || 'unnamed file'); + + if (!extra.projects || extra.projects.length !== 1) { + return undefined; + } + + const tsconfigPath = getTsconfigPath(extra.projects[0], extra); + + const commandLine = ts.getParsedCommandLineOfConfigFile( + tsconfigPath, + DEFAULT_COMPILER_OPTIONS, + { ...ts.sys, onUnRecoverableConfigFileDiagnostic: () => {} }, + ); + + if (!commandLine) { + return undefined; + } + + const compilerHost = ts.createCompilerHost(commandLine.options, true); + const oldReadFile = compilerHost.readFile; + compilerHost.readFile = (fileName: string): string | undefined => + path.normalize(fileName) === path.normalize(extra.filePath) + ? code + : oldReadFile(fileName); + + const program = ts.createProgram( + [extra.filePath], + commandLine.options, + compilerHost, + ); + const ast = program.getSourceFile(extra.filePath); + + return ast && { ast, program }; +} + +export { createDefaultProgram }; diff --git a/packages/typescript-estree/src/create-program/createIsolatedProgram.ts b/packages/typescript-estree/src/create-program/createIsolatedProgram.ts new file mode 100644 index 000000000000..8588b3c1bc43 --- /dev/null +++ b/packages/typescript-estree/src/create-program/createIsolatedProgram.ts @@ -0,0 +1,71 @@ +import debug from 'debug'; +import ts from 'typescript'; +import { Extra } from '../parser-options'; +import { ASTAndProgram, DEFAULT_COMPILER_OPTIONS } from './shared'; + +const log = debug('typescript-eslint:typescript-estree:createIsolatedProgram'); + +/** + * @param code The code of the file being linted + * @returns Returns a new source file and program corresponding to the linted code + */ +function createIsolatedProgram(code: string, extra: Extra): ASTAndProgram { + log('Getting isolated program for: %s', extra.filePath); + + const compilerHost: ts.CompilerHost = { + fileExists() { + return true; + }, + getCanonicalFileName() { + return extra.filePath; + }, + getCurrentDirectory() { + return ''; + }, + getDirectories() { + return []; + }, + getDefaultLibFileName() { + return 'lib.d.ts'; + }, + + // TODO: Support Windows CRLF + getNewLine() { + return '\n'; + }, + getSourceFile(filename: string) { + return ts.createSourceFile(filename, code, ts.ScriptTarget.Latest, true); + }, + readFile() { + return undefined; + }, + useCaseSensitiveFileNames() { + return true; + }, + writeFile() { + return null; + }, + }; + + const program = ts.createProgram( + [extra.filePath], + { + noResolve: true, + target: ts.ScriptTarget.Latest, + jsx: extra.jsx ? ts.JsxEmit.Preserve : undefined, + ...DEFAULT_COMPILER_OPTIONS, + }, + compilerHost, + ); + + const ast = program.getSourceFile(extra.filePath); + if (!ast) { + throw new Error( + 'Expected an ast to be returned for the single-file isolated program.', + ); + } + + return { ast, program }; +} + +export { createIsolatedProgram }; diff --git a/packages/typescript-estree/src/create-program/createProjectProgram.ts b/packages/typescript-estree/src/create-program/createProjectProgram.ts new file mode 100644 index 000000000000..9864c20cea01 --- /dev/null +++ b/packages/typescript-estree/src/create-program/createProjectProgram.ts @@ -0,0 +1,72 @@ +import debug from 'debug'; +import path from 'path'; +import { getProgramsForProjects } from './createWatchProgram'; +import { firstDefined } from '../node-utils'; +import { Extra } from '../parser-options'; +import { ASTAndProgram } from './shared'; + +const log = debug('typescript-eslint:typescript-estree:createProjectProgram'); + +/** + * @param code The code of the file being linted + * @param options The config object + * @returns If found, returns the source file corresponding to the code and the containing program + */ +function createProjectProgram( + code: string, + createDefaultProgram: boolean, + extra: Extra, +): ASTAndProgram | undefined { + log('Creating project program for: %s', extra.filePath); + + const astAndProgram = firstDefined( + getProgramsForProjects(code, extra.filePath, extra), + currentProgram => { + const ast = currentProgram.getSourceFile(extra.filePath); + return ast && { ast, program: currentProgram }; + }, + ); + + if (!astAndProgram && !createDefaultProgram) { + // the file was either not matched within the tsconfig, or the extension wasn't expected + const errorLines = [ + '"parserOptions.project" has been set for @typescript-eslint/parser.', + `The file does not match your project config: ${path.relative( + process.cwd(), + extra.filePath, + )}.`, + ]; + let hasMatchedAnError = false; + + const fileExtension = path.extname(extra.filePath); + if (!['.ts', '.tsx', '.js', '.jsx'].includes(fileExtension)) { + const nonStandardExt = `The extension for the file (${fileExtension}) is non-standard`; + if (extra.extraFileExtensions && extra.extraFileExtensions.length > 0) { + if (!extra.extraFileExtensions.includes(fileExtension)) { + errorLines.push( + `${nonStandardExt}. It should be added to your existing "parserOptions.extraFileExtensions".`, + ); + hasMatchedAnError = true; + } + } else { + errorLines.push( + `${nonStandardExt}. You should add "parserOptions.extraFileExtensions" to your config.`, + ); + hasMatchedAnError = true; + } + } + + if (!hasMatchedAnError) { + errorLines.push( + 'The file must be included in at least one of the projects provided.', + ); + hasMatchedAnError = true; + } + + throw new Error(errorLines.join('\n')); + } + + return astAndProgram; +} + +export { createProjectProgram }; diff --git a/packages/typescript-estree/src/create-program/createSourceFile.ts b/packages/typescript-estree/src/create-program/createSourceFile.ts new file mode 100644 index 000000000000..9f14ec274ff1 --- /dev/null +++ b/packages/typescript-estree/src/create-program/createSourceFile.ts @@ -0,0 +1,18 @@ +import debug from 'debug'; +import ts from 'typescript'; +import { Extra } from '../parser-options'; + +const log = debug('typescript-eslint:typescript-estree:createIsolatedProgram'); + +function createSourceFile(code: string, extra: Extra): ts.SourceFile { + log('Getting AST without type information for: %s', extra.filePath); + + return ts.createSourceFile( + extra.filePath, + code, + ts.ScriptTarget.Latest, + /* setParentNodes */ true, + ); +} + +export { createSourceFile }; diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts new file mode 100644 index 000000000000..4bfde8d2ba87 --- /dev/null +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -0,0 +1,440 @@ +import debug from 'debug'; +import fs from 'fs'; +import path from 'path'; +import ts from 'typescript'; +import { Extra } from '../parser-options'; +import { WatchCompilerHostOfConfigFile } from '../WatchCompilerHostOfConfigFile'; +import { getTsconfigPath, DEFAULT_COMPILER_OPTIONS } from './shared'; + +const log = debug('typescript-eslint:typescript-estree:createWatchProgram'); + +/** + * Maps tsconfig paths to their corresponding file contents and resulting watches + */ +const knownWatchProgramMap = new Map< + string, + ts.WatchOfConfigFile +>(); + +/** + * Maps file/folder paths to their set of corresponding watch callbacks + * There may be more than one per file/folder if a file/folder is shared between projects + */ +const fileWatchCallbackTrackingMap = new Map< + string, + Set +>(); +const folderWatchCallbackTrackingMap = new Map< + string, + Set +>(); + +/** + * Stores the list of known files for each program + */ +const programFileListCache = new Map>(); + +/** + * Caches the last modified time of the tsconfig files + */ +const tsconfigLsatModifiedTimestampCache = new Map(); + +const parsedFilesSeen = new Set(); + +/** + * Clear all of the parser caches. + * This should only be used in testing to ensure the parser is clean between tests. + */ +function clearCaches(): void { + knownWatchProgramMap.clear(); + fileWatchCallbackTrackingMap.clear(); + folderWatchCallbackTrackingMap.clear(); + parsedFilesSeen.clear(); + programFileListCache.clear(); + tsconfigLsatModifiedTimestampCache.clear(); +} + +function saveWatchCallback( + trackingMap: Map>, +) { + return ( + fileName: string, + callback: ts.FileWatcherCallback, + ): ts.FileWatcher => { + const normalizedFileName = path.normalize(fileName); + const watchers = ((): Set => { + let watchers = trackingMap.get(normalizedFileName); + if (!watchers) { + watchers = new Set(); + trackingMap.set(normalizedFileName, watchers); + } + return watchers; + })(); + watchers.add(callback); + + return { + close: (): void => { + watchers.delete(callback); + }, + }; + }; +} + +/** + * Holds information about the file currently being linted + */ +const currentLintOperationState = { + code: '', + filePath: '', +}; + +/** + * Appropriately report issues found when reading a config file + * @param diagnostic The diagnostic raised when creating a program + */ +function diagnosticReporter(diagnostic: ts.Diagnostic): void { + throw new Error( + ts.flattenDiagnosticMessageText(diagnostic.messageText, ts.sys.newLine), + ); +} + +/** + * Calculate project environments using options provided by consumer and paths from config + * @param code The code being linted + * @param filePath The path of the file being parsed + * @param extra.tsconfigRootDir The root directory for relative tsconfig paths + * @param extra.projects Provided tsconfig paths + * @returns The programs corresponding to the supplied tsconfig paths + */ +function getProgramsForProjects( + code: string, + filePath: string, + extra: Extra, +): ts.Program[] { + const results = []; + + // preserve reference to code and file being linted + currentLintOperationState.code = code; + currentLintOperationState.filePath = filePath; + + // Update file version if necessary + // TODO: only update when necessary, currently marks as changed on every lint + const fileWatchCallbacks = fileWatchCallbackTrackingMap.get(filePath); + if ( + parsedFilesSeen.has(filePath) && + fileWatchCallbacks && + fileWatchCallbacks.size > 0 + ) { + fileWatchCallbacks.forEach(cb => + cb(filePath, ts.FileWatcherEventKind.Changed), + ); + } + + /* + * before we go into the process of attempting to find and update every program + * see if we know of a program that contains this file + */ + for (const rawTsconfigPath of extra.projects) { + const tsconfigPath = getTsconfigPath(rawTsconfigPath, extra); + const existingWatch = knownWatchProgramMap.get(tsconfigPath); + if (!existingWatch) { + continue; + } + + let fileList = programFileListCache.get(tsconfigPath); + let updatedProgram: ts.Program | null = null; + if (!fileList) { + updatedProgram = existingWatch.getProgram().getProgram(); + fileList = new Set(updatedProgram.getRootFileNames()); + programFileListCache.set(tsconfigPath, fileList); + } + + if (fileList.has(filePath)) { + log('Found existing program for file. %s', filePath); + return [updatedProgram || existingWatch.getProgram().getProgram()]; + } + } + log( + 'File did not belong to any existing programs, moving to create/update. %s', + filePath, + ); + + /* + * We don't know of a program that contains the file, this means that either: + * - the required program hasn't been created yet, or + * - the file is new/renamed, and the program hasn't been updated. + */ + for (const rawTsconfigPath of extra.projects) { + const tsconfigPath = getTsconfigPath(rawTsconfigPath, extra); + + const existingWatch = knownWatchProgramMap.get(tsconfigPath); + + if (existingWatch) { + const updatedProgram = maybeInvalidateProgram( + existingWatch, + filePath, + tsconfigPath, + ); + if (!updatedProgram) { + continue; + } + + // sets parent pointers in source files + updatedProgram.getTypeChecker(); + results.push(updatedProgram); + + continue; + } + + const programWatch = createWatchProgram(tsconfigPath, extra); + const program = programWatch.getProgram().getProgram(); + + // cache watch program and return current program + knownWatchProgramMap.set(tsconfigPath, programWatch); + results.push(program); + } + + parsedFilesSeen.add(filePath); + return results; +} + +function createWatchProgram( + tsconfigPath: string, + extra: Extra, +): ts.WatchOfConfigFile { + log('Creating watch program for %s.', tsconfigPath); + + // create compiler host + const watchCompilerHost = ts.createWatchCompilerHost( + tsconfigPath, + DEFAULT_COMPILER_OPTIONS, + ts.sys, + ts.createSemanticDiagnosticsBuilderProgram, + diagnosticReporter, + /*reportWatchStatus*/ () => {}, + ) as WatchCompilerHostOfConfigFile; + + // ensure readFile reads the code being linted instead of the copy on disk + const oldReadFile = watchCompilerHost.readFile; + watchCompilerHost.readFile = (filePath, encoding): string | undefined => + path.normalize(filePath) === + path.normalize(currentLintOperationState.filePath) + ? currentLintOperationState.code + : oldReadFile(filePath, encoding); + + // ensure process reports error on failure instead of exiting process immediately + watchCompilerHost.onUnRecoverableConfigFileDiagnostic = diagnosticReporter; + + // ensure process doesn't emit programs + watchCompilerHost.afterProgramCreate = (program): void => { + // report error if there are any errors in the config file + const configFileDiagnostics = program + .getConfigFileParsingDiagnostics() + .filter( + diag => + diag.category === ts.DiagnosticCategory.Error && diag.code !== 18003, + ); + if (configFileDiagnostics.length > 0) { + diagnosticReporter(configFileDiagnostics[0]); + } + }; + + /* + * From the CLI, the file watchers won't matter, as the files will be parsed once and then forgotten. + * When running from an IDE, these watchers will let us tell typescript about changes. + * + * ESLint IDE plugins will send us unfinished file content as the user types (before it's saved to disk). + * We use the file watchers to tell typescript about this latest file content. + * + * When files are created (or renamed), we won't know about them because we have no filesystem watchers attached. + * We use the folder watchers to tell typescript it needs to go and find new files in the project folders. + */ + watchCompilerHost.watchFile = saveWatchCallback(fileWatchCallbackTrackingMap); + watchCompilerHost.watchDirectory = saveWatchCallback( + folderWatchCallbackTrackingMap, + ); + + // allow files with custom extensions to be included in program (uses internal ts api) + const oldOnDirectoryStructureHostCreate = + watchCompilerHost.onCachedDirectoryStructureHostCreate; + watchCompilerHost.onCachedDirectoryStructureHostCreate = (host): void => { + const oldReadDirectory = host.readDirectory; + host.readDirectory = ( + path, + extensions, + exclude, + include, + depth, + ): string[] => + oldReadDirectory( + path, + !extensions ? undefined : extensions.concat(extra.extraFileExtensions), + exclude, + include, + depth, + ); + oldOnDirectoryStructureHostCreate(host); + }; + + /* + * The watch change callbacks TS provides us all have a 250ms delay before firing + * https://github.com/microsoft/TypeScript/blob/b845800bdfcc81c8c72e2ac6fdc2c1df0cdab6f9/src/compiler/watch.ts#L1013 + * + * We live in a synchronous world, so we can't wait for that. + * This is a bit of a hack, but it lets us immediately force updates when we detect a tsconfig or directory change + */ + const oldSetTimeout = watchCompilerHost.setTimeout; + watchCompilerHost.setTimeout = (cb, ms, ...args): unknown => { + if (ms === 250) { + cb(); + return null; + } + + return oldSetTimeout && oldSetTimeout(cb, ms, ...args); + }; + + return ts.createWatchProgram(watchCompilerHost); +} + +function hasTSConfigChanged(tsconfigPath: string): boolean { + const stat = fs.statSync(tsconfigPath); + const lastModifiedAt = stat.mtimeMs; + const cachedLastModifiedAt = tsconfigLsatModifiedTimestampCache.get( + tsconfigPath, + ); + + tsconfigLsatModifiedTimestampCache.set(tsconfigPath, lastModifiedAt); + + if (cachedLastModifiedAt === undefined) { + return false; + } + + return Math.abs(cachedLastModifiedAt - lastModifiedAt) > Number.EPSILON; +} + +function maybeInvalidateProgram( + existingWatch: ts.WatchOfConfigFile, + filePath: string, + tsconfigPath: string, +): ts.Program | null { + /* + * By calling watchProgram.getProgram(), it will trigger a resync of the program based on + * whatever new file content we've given it from our input. + */ + let updatedProgram = existingWatch.getProgram().getProgram(); + + // In case this change causes problems in larger real world codebases + // Provide an escape hatch so people don't _have_ to revert to an older version + if (process.env.TSESTREE_NO_INVALIDATION === 'true') { + return updatedProgram; + } + + if (hasTSConfigChanged(tsconfigPath)) { + /* + * If the stat of the tsconfig has changed, that could mean the include/exclude/files lists has changed + * We need to make sure typescript knows this so it can update appropriately + */ + log('tsconfig has changed - triggering program update. %s', tsconfigPath); + fileWatchCallbackTrackingMap + .get(tsconfigPath)! + .forEach(cb => cb(tsconfigPath, ts.FileWatcherEventKind.Changed)); + + // tsconfig change means that the file list more than likely changed, so clear the cache + programFileListCache.delete(tsconfigPath); + } + + let sourceFile = updatedProgram.getSourceFile(filePath); + if (sourceFile) { + return updatedProgram; + } + /* + * Missing source file means our program's folder structure might be out of date. + * So we need to tell typescript it needs to update the correct folder. + */ + log('File was not found in program - triggering folder update. %s', filePath); + + // Find the correct directory callback by climbing the folder tree + let current: string | null = null; + let next: string | null = path.dirname(filePath); + let hasCallback = false; + while (current !== next) { + current = next; + const folderWatchCallbacks = folderWatchCallbackTrackingMap.get(current); + if (folderWatchCallbacks) { + folderWatchCallbacks.forEach(cb => + cb(current!, ts.FileWatcherEventKind.Changed), + ); + hasCallback = true; + break; + } + + next = path.dirname(current); + } + if (!hasCallback) { + /* + * No callback means the paths don't matchup - so no point returning any program + * this will signal to the caller to skip this program + */ + log('No callback found for file, not part of this program. %s', filePath); + return null; + } + + // directory update means that the file list more than likely changed, so clear the cache + programFileListCache.delete(tsconfigPath); + + // force the immediate resync + updatedProgram = existingWatch.getProgram().getProgram(); + sourceFile = updatedProgram.getSourceFile(filePath); + if (sourceFile) { + return updatedProgram; + } + + /* + * At this point we're in one of two states: + * - The file isn't supposed to be in this program due to exclusions + * - The file is new, and was renamed from an old, included filename + * + * For the latter case, we need to tell typescript that the old filename is now deleted + */ + log( + 'File was still not found in program after directory update - checking file deletions. %s', + filePath, + ); + + const rootFilenames = updatedProgram.getRootFileNames(); + // use find because we only need to "delete" one file to cause typescript to do a full resync + const deletedFile = rootFilenames.find(file => !fs.existsSync(file)); + if (!deletedFile) { + // There are no deleted files, so it must be the former case of the file not belonging to this program + return null; + } + + const fileWatchCallbacks = fileWatchCallbackTrackingMap.get(deletedFile); + if (!fileWatchCallbacks) { + // shouldn't happen, but just in case + log('Could not find watch callbacks for root file. %s', deletedFile); + return updatedProgram; + } + + log('Marking file as deleted. %s', deletedFile); + fileWatchCallbacks.forEach(cb => + cb(deletedFile, ts.FileWatcherEventKind.Deleted), + ); + + // deleted files means that the file list _has_ changed, so clear the cache + programFileListCache.delete(tsconfigPath); + + updatedProgram = existingWatch.getProgram().getProgram(); + sourceFile = updatedProgram.getSourceFile(filePath); + if (sourceFile) { + return updatedProgram; + } + + log( + 'File was still not found in program after deletion check, assuming it is not part of this program. %s', + filePath, + ); + return null; +} + +export { clearCaches, createWatchProgram, getProgramsForProjects }; diff --git a/packages/typescript-estree/src/create-program/shared.ts b/packages/typescript-estree/src/create-program/shared.ts new file mode 100644 index 000000000000..3607a7fb897c --- /dev/null +++ b/packages/typescript-estree/src/create-program/shared.ts @@ -0,0 +1,27 @@ +import path from 'path'; +import ts from 'typescript'; +import { Extra } from '../parser-options'; + +interface ASTAndProgram { + ast: ts.SourceFile; + program: ts.Program | undefined; +} + +/** + * Default compiler options for program generation from single root file + */ +const DEFAULT_COMPILER_OPTIONS: ts.CompilerOptions = { + allowNonTsExtensions: true, + allowJs: true, + checkJs: true, + noEmit: true, + // extendedDiagnostics: true, +}; + +function getTsconfigPath(tsconfigPath: string, extra: Extra): string { + return path.isAbsolute(tsconfigPath) + ? tsconfigPath + : path.join(extra.tsconfigRootDir || process.cwd(), tsconfigPath); +} + +export { ASTAndProgram, DEFAULT_COMPILER_OPTIONS, getTsconfigPath }; diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index 1a47f30f16d6..63e14a2fffb1 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -9,10 +9,10 @@ export interface Extra { errorOnTypeScriptSyntacticAndSemanticIssues: boolean; errorOnUnknownASTType: boolean; extraFileExtensions: string[]; + filePath: string; jsx: boolean; loc: boolean; log: Function; - noWatch?: boolean; preserveNodeMaps?: boolean; projects: string[]; range: boolean; @@ -32,7 +32,6 @@ export interface TSESTreeOptions { jsx?: boolean; loc?: boolean; loggerFn?: Function | false; - noWatch?: boolean; preserveNodeMaps?: boolean; project?: string | string[]; range?: boolean; diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 7efde256c81d..cb56c179a004 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -1,28 +1,22 @@ -import debug from 'debug'; -import path from 'path'; import semver from 'semver'; import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import { sync as globSync } from 'glob'; import isGlob from 'is-glob'; import { astConverter } from './ast-converter'; import { convertError } from './convert'; -import { firstDefined } from './node-utils'; +import { createDefaultProgram } from './create-program/createDefaultProgram'; +import { createIsolatedProgram } from './create-program/createIsolatedProgram'; +import { createProjectProgram } from './create-program/createProjectProgram'; +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 { - calculateProjectParserOptions, - createProgram, - defaultCompilerOptions, -} from './tsconfig-parser'; - -const log = debug('typescript-eslint:typescript-estree:parser'); /** * This needs to be kept in sync with the top-level README.md in the * typescript-eslint monorepo */ -const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.2.1 <3.8.0'; +const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.2.1 <3.8.0 >3.7.0-dev.0'; const ACTIVE_TYPESCRIPT_VERSION = ts.version; const isRunningSupportedTypeScriptVersion = semver.satisfies( ACTIVE_TYPESCRIPT_VERSION, @@ -32,18 +26,6 @@ const isRunningSupportedTypeScriptVersion = semver.satisfies( let extra: Extra; let warnedAboutTSVersion = false; -/** - * Compute the filename based on the parser options. - * - * Even if jsx option is set in typescript compiler, filename still has to - * contain .tsx file extension. - * - * @param options Parser options - */ -function getFileName({ jsx }: { jsx?: boolean }): string { - return jsx ? 'estree.tsx' : 'estree.ts'; -} - function enforceString(code: unknown): string { /** * Ensure the source code is a string @@ -55,6 +37,44 @@ 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 options The config object + * @param shouldProvideParserServices True iff the program should be attempted to be calculated from provided tsconfig files + * @returns Returns a source file and program corresponding to the linted code + */ +function getProgramAndAST( + code: string, + shouldProvideParserServices: boolean, + shouldCreateDefaultProgram: boolean, +): ASTAndProgram | undefined { + return ( + (shouldProvideParserServices && + createProjectProgram(code, shouldCreateDefaultProgram, extra)) || + (shouldProvideParserServices && + shouldCreateDefaultProgram && + createDefaultProgram(code, extra)) || + createIsolatedProgram(code, extra) + ); +} + +/** + * Compute the filename based on the parser options. + * + * Even if jsx option is set in typescript compiler, filename still has to + * contain .tsx file extension. + * + * @param options Parser options + */ +function getFileName({ jsx }: { jsx?: boolean } = {}): string { + return jsx ? 'estree.tsx' : 'estree.ts'; +} + /** * Resets the extra config object */ @@ -67,10 +87,10 @@ function resetExtra(): void { errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: false, extraFileExtensions: [], + filePath: getFileName(), jsx: false, loc: false, log: console.log, // eslint-disable-line no-console - noWatch: false, preserveNodeMaps: undefined, projects: [], range: false, @@ -81,190 +101,20 @@ function resetExtra(): void { }; } -interface ASTAndProgram { - ast: ts.SourceFile; - program: ts.Program | undefined; -} - -/** - * @param code The code of the file being linted - * @param options The config object - * @returns If found, returns the source file corresponding to the code and the containing program - */ -function getASTFromProject( - code: string, - options: TSESTreeOptions, - createDefaultProgram: boolean, -): ASTAndProgram | undefined { - log('Attempting to get AST from project(s) for: %s', options.filePath); - - const filePath = options.filePath || getFileName(options); - const astAndProgram = firstDefined( - calculateProjectParserOptions(code, filePath, extra), - currentProgram => { - const ast = currentProgram.getSourceFile(filePath); - return ast && { ast, program: currentProgram }; - }, - ); - - if (!astAndProgram && !createDefaultProgram) { - // the file was either not matched within the tsconfig, or the extension wasn't expected - const errorLines = [ - '"parserOptions.project" has been set for @typescript-eslint/parser.', - `The file does not match your project config: ${filePath}.`, - ]; - let hasMatchedAnError = false; - - const fileExtension = path.extname(filePath); - if (!['.ts', '.tsx', '.js', '.jsx'].includes(fileExtension)) { - const nonStandardExt = `The extension for the file (${fileExtension}) is non-standard`; - if (extra.extraFileExtensions && extra.extraFileExtensions.length > 0) { - if (!extra.extraFileExtensions.includes(fileExtension)) { - errorLines.push( - `${nonStandardExt}. It should be added to your existing "parserOptions.extraFileExtensions".`, - ); - hasMatchedAnError = true; - } - } else { - errorLines.push( - `${nonStandardExt}. You should add "parserOptions.extraFileExtensions" to your config.`, - ); - hasMatchedAnError = true; - } - } - - if (!hasMatchedAnError) { - errorLines.push( - 'The file must be included in at least one of the projects provided.', - ); - hasMatchedAnError = true; - } - - throw new Error(errorLines.join('\n')); - } - - return astAndProgram; -} - -/** - * @param code The code of the file being linted - * @param options The config object - * @returns If found, returns the source file corresponding to the code and the containing program - */ -function getASTAndDefaultProject( - code: string, - options: TSESTreeOptions, -): ASTAndProgram | undefined { - log( - 'Attempting to get AST from the default project(s): %s', - options.filePath, - ); - - const fileName = options.filePath || getFileName(options); - const program = createProgram(code, fileName, extra); - const ast = program && program.getSourceFile(fileName); - return ast && { ast, program }; -} - -/** - * @param code The code of the file being linted - * @returns Returns a new source file and program corresponding to the linted code - */ -function createNewProgram(code: string): ASTAndProgram { - log('Getting AST without type information'); - - const FILENAME = getFileName(extra); - - const compilerHost: ts.CompilerHost = { - fileExists() { - return true; - }, - getCanonicalFileName() { - return FILENAME; - }, - getCurrentDirectory() { - return ''; - }, - getDirectories() { - return []; - }, - getDefaultLibFileName() { - return 'lib.d.ts'; - }, - - // TODO: Support Windows CRLF - getNewLine() { - return '\n'; - }, - getSourceFile(filename: string) { - return ts.createSourceFile(filename, code, ts.ScriptTarget.Latest, true); - }, - readFile() { - return undefined; - }, - useCaseSensitiveFileNames() { - return true; - }, - writeFile() { - return null; - }, - }; - - const program = ts.createProgram( - [FILENAME], - { - noResolve: true, - target: ts.ScriptTarget.Latest, - jsx: extra.jsx ? ts.JsxEmit.Preserve : undefined, - ...defaultCompilerOptions, - }, - compilerHost, - ); - - const ast = program.getSourceFile(FILENAME)!; - - return { ast, program }; -} - -/** - * @param code The code of the file being linted - * @param options The config object - * @param shouldProvideParserServices True iff the program should be attempted to be calculated from provided tsconfig files - * @returns Returns a source file and program corresponding to the linted code - */ -function getProgramAndAST( - code: string, - options: TSESTreeOptions, - shouldProvideParserServices: boolean, - createDefaultProgram: boolean, -): ASTAndProgram | undefined { - return ( - (shouldProvideParserServices && - getASTFromProject(code, options, createDefaultProgram)) || - (shouldProvideParserServices && - createDefaultProgram && - getASTAndDefaultProject(code, options)) || - createNewProgram(code) - ); -} - function applyParserOptionsToExtra(options: TSESTreeOptions): void { - /** - * Turn on/off filesystem watchers - */ - extra.noWatch = typeof options.noWatch === 'boolean' && options.noWatch; - /** * Track range information in the AST */ extra.range = typeof options.range === 'boolean' && options.range; extra.loc = typeof options.loc === 'boolean' && options.loc; + /** * Track tokens in the AST */ if (typeof options.tokens === 'boolean' && options.tokens) { extra.tokens = []; } + /** * Track comments in the AST */ @@ -272,12 +122,23 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { extra.comment = true; extra.comments = []; } + /** * Enable JSX - note the applicable file extension is still required */ if (typeof options.jsx === 'boolean' && options.jsx) { extra.jsx = true; } + + /** + * Get the file extension + */ + if (typeof options.filePath === 'string' && options.filePath !== '') { + extra.filePath = options.filePath; + } else { + extra.filePath = getFileName(extra); + } + /** * The JSX AST changed the node type for string literals * inside a JSX Element from `Literal` to `JSXText`. @@ -288,6 +149,7 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { if (typeof options.useJSXTextNode === 'boolean' && options.useJSXTextNode) { extra.useJSXTextNode = true; } + /** * Allow the user to cause the parser to error if it encounters an unknown AST Node Type * (used in testing) @@ -298,6 +160,7 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { ) { extra.errorOnUnknownASTType = true; } + /** * Allow the user to override the function used for logging */ @@ -341,6 +204,7 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { ) { extra.extraFileExtensions = options.extraFileExtensions; } + /** * Allow the user to enable or disable the preservation of the AST node maps * during the conversion process. @@ -380,12 +244,12 @@ function warnAboutTSVersion(): void { // Parser //------------------------------------------------------------------------------ -export type AST = TSESTree.Program & +type AST = TSESTree.Program & (T['range'] extends true ? { range: [number, number] } : {}) & (T['tokens'] extends true ? { tokens: TSESTree.Token[] } : {}) & (T['comment'] extends true ? { comments: TSESTree.Comment[] } : {}); -export interface ParseAndGenerateServicesResult { +interface ParseAndGenerateServicesResult { ast: AST; services: ParserServices; } @@ -394,9 +258,9 @@ export interface ParseAndGenerateServicesResult { // Public //------------------------------------------------------------------------------ -export const version: string = require('../package.json').version; +const version: string = require('../package.json').version; -export function parse( +function parse( code: string, options?: T, ): AST { @@ -436,12 +300,7 @@ export function parse( * Create a ts.SourceFile directly, no ts.Program is needed for a simple * parse */ - const ast = ts.createSourceFile( - getFileName(extra), - code, - ts.ScriptTarget.Latest, - /* setParentNodes */ true, - ); + const ast = createSourceFile(code, extra); /** * Convert the TypeScript AST to an ESTree-compatible one @@ -450,9 +309,10 @@ export function parse( return estree as AST; } -export function parseAndGenerateServices< - T extends TSESTreeOptions = TSESTreeOptions ->(code: string, options: T): ParseAndGenerateServicesResult { +function parseAndGenerateServices( + code: string, + options: T, +): ParseAndGenerateServicesResult { /** * Reset the parse configuration */ @@ -491,7 +351,6 @@ export function parseAndGenerateServices< extra.projects && extra.projects.length > 0; const { ast, program } = getProgramAndAST( code, - options, shouldProvideParserServices, extra.createDefaultProgram, )!; @@ -541,6 +400,14 @@ export function parseAndGenerateServices< }; } -export { TSESTreeOptions, ParserServices }; +export { + AST, + parse, + parseAndGenerateServices, + ParseAndGenerateServicesResult, + ParserServices, + TSESTreeOptions, + version, +}; export * from './ts-estree'; -export { clearCaches } from './tsconfig-parser'; +export { clearCaches } from './create-program/createWatchProgram'; diff --git a/packages/typescript-estree/src/tsconfig-parser.ts b/packages/typescript-estree/src/tsconfig-parser.ts deleted file mode 100644 index 5f3b97c036ea..000000000000 --- a/packages/typescript-estree/src/tsconfig-parser.ts +++ /dev/null @@ -1,391 +0,0 @@ -import chokidar from 'chokidar'; -import debug from 'debug'; -import path from 'path'; -import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports -import { Extra } from './parser-options'; -import { WatchCompilerHostOfConfigFile } from './WatchCompilerHostOfConfigFile'; - -const log = debug('typescript-eslint:typescript-estree:tsconfig-parser'); - -/** - * Default compiler options for program generation from single root file - */ -export const defaultCompilerOptions: ts.CompilerOptions = { - allowNonTsExtensions: true, - allowJs: true, - checkJs: true, - noEmit: true, - // extendedDiagnostics: true, -}; - -/** - * Maps tsconfig paths to their corresponding file contents and resulting watches - */ -const knownWatchProgramMap = new Map< - string, - ts.WatchOfConfigFile ->(); - -/** - * Maps file paths to their set of corresponding watch callbacks - * There may be more than one per file if a file is shared between projects - */ -const watchCallbackTrackingMap = new Map>(); - -interface Watcher { - close(): void; - forceClose(): void; - on(evt: 'add', listener: (file: string) => void): void; - on(evt: 'change', listener: (file: string) => void): void; - trackWatcher(): void; -} -/** - * Tracks the ts.sys.watchDirectory watchers that we've opened for project folders. - * We store these so we can clean up our handles if required. - */ -const fileWatcherTrackingSet = new Map(); - -const parsedFilesSeen = new Set(); - -/** - * Clear all of the parser caches. - * This should only be used in testing to ensure the parser is clean between tests. - */ -export function clearCaches(): void { - knownWatchProgramMap.clear(); - watchCallbackTrackingMap.clear(); - parsedFilesSeen.clear(); - - // stop tracking config files - fileWatcherTrackingSet.forEach(cb => cb.forceClose()); - fileWatcherTrackingSet.clear(); -} - -/** - * Holds information about the file currently being linted - */ -const currentLintOperationState = { - code: '', - filePath: '', -}; - -/** - * Appropriately report issues found when reading a config file - * @param diagnostic The diagnostic raised when creating a program - */ -function diagnosticReporter(diagnostic: ts.Diagnostic): void { - throw new Error( - ts.flattenDiagnosticMessageText(diagnostic.messageText, ts.sys.newLine), - ); -} - -function getTsconfigPath(tsconfigPath: string, extra: Extra): string { - return path.isAbsolute(tsconfigPath) - ? tsconfigPath - : path.join(extra.tsconfigRootDir || process.cwd(), tsconfigPath); -} - -const EMPTY_WATCHER: Watcher = { - close: (): void => {}, - forceClose: (): void => {}, - on: (): void => {}, - trackWatcher: (): void => {}, -}; - -/** - * Watches a file or directory for changes - */ -function watch( - watchPath: string, - options: chokidar.WatchOptions, - extra: Extra, -): Watcher { - // an escape hatch to disable the file watchers as they can take a bit to initialise in some cases - // this also supports an env variable so it's easy to switch on/off from the CLI - const blockWatchers = - process.env.PARSER_NO_WATCH === 'false' - ? false - : process.env.PARSER_NO_WATCH === 'true' || extra.noWatch === true; - if (blockWatchers) { - return EMPTY_WATCHER; - } - - // reuse watchers in case typescript asks us to watch the same file/directory multiple times - if (fileWatcherTrackingSet.has(watchPath)) { - const watcher = fileWatcherTrackingSet.get(watchPath)!; - watcher.trackWatcher(); - return watcher; - } - - let fsWatcher: chokidar.FSWatcher; - try { - log('setting up watcher on path: %s', watchPath); - fsWatcher = chokidar.watch(watchPath, { - ignoreInitial: true, - persistent: false, - useFsEvents: false, - ...options, - }); - } catch (e) { - log( - 'error occurred using file watcher, setting up polling watcher instead: %s', - watchPath, - ); - // https://github.com/microsoft/TypeScript/blob/c9d407b52ad92370cd116105c33d618195de8070/src/compiler/sys.ts#L1232-L1237 - // Catch the exception and use polling instead - // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point - // so instead of throwing error, use fs.watchFile - fsWatcher = chokidar.watch(watchPath, { - ignoreInitial: true, - persistent: false, - useFsEvents: false, - ...options, - usePolling: true, - }); - } - - let counter = 1; - const watcher = { - close: (): void => { - counter -= 1; - if (counter <= 0) { - fsWatcher.close(); - fileWatcherTrackingSet.delete(watchPath); - } - }, - forceClose: fsWatcher.close.bind(fsWatcher), - on: fsWatcher.on.bind(fsWatcher), - trackWatcher: (): void => { - counter += 1; - }, - }; - - fileWatcherTrackingSet.set(watchPath, watcher); - - return watcher; -} - -/** - * Calculate project environments using options provided by consumer and paths from config - * @param code The code being linted - * @param filePath The path of the file being parsed - * @param extra.tsconfigRootDir The root directory for relative tsconfig paths - * @param extra.projects Provided tsconfig paths - * @returns The programs corresponding to the supplied tsconfig paths - */ -export function calculateProjectParserOptions( - code: string, - filePath: string, - extra: Extra, -): ts.Program[] { - const results = []; - - // preserve reference to code and file being linted - currentLintOperationState.code = code; - currentLintOperationState.filePath = filePath; - - // Update file version if necessary - // TODO: only update when necessary, currently marks as changed on every lint - const watchCallbacks = watchCallbackTrackingMap.get(filePath); - if ( - parsedFilesSeen.has(filePath) && - watchCallbacks && - watchCallbacks.size > 0 - ) { - watchCallbacks.forEach(cb => cb(filePath, ts.FileWatcherEventKind.Changed)); - } - - for (const rawTsconfigPath of extra.projects) { - const tsconfigPath = getTsconfigPath(rawTsconfigPath, extra); - - const existingWatch = knownWatchProgramMap.get(tsconfigPath); - - if (typeof existingWatch !== 'undefined') { - // get new program (updated if necessary) - const updatedProgram = existingWatch.getProgram().getProgram(); - updatedProgram.getTypeChecker(); // sets parent pointers in source files - results.push(updatedProgram); - - continue; - } - - // create compiler host - const watchCompilerHost = ts.createWatchCompilerHost( - tsconfigPath, - defaultCompilerOptions, - ts.sys, - ts.createSemanticDiagnosticsBuilderProgram, - diagnosticReporter, - /*reportWatchStatus*/ () => {}, - ) as WatchCompilerHostOfConfigFile; - - // ensure readFile reads the code being linted instead of the copy on disk - const oldReadFile = watchCompilerHost.readFile; - watchCompilerHost.readFile = (filePath, encoding): string | undefined => - path.normalize(filePath) === - path.normalize(currentLintOperationState.filePath) - ? currentLintOperationState.code - : oldReadFile(filePath, encoding); - - // ensure process reports error on failure instead of exiting process immediately - watchCompilerHost.onUnRecoverableConfigFileDiagnostic = diagnosticReporter; - - // ensure process doesn't emit programs - watchCompilerHost.afterProgramCreate = (program): void => { - // report error if there are any errors in the config file - const configFileDiagnostics = program - .getConfigFileParsingDiagnostics() - .filter( - diag => - diag.category === ts.DiagnosticCategory.Error && - diag.code !== 18003, - ); - if (configFileDiagnostics.length > 0) { - diagnosticReporter(configFileDiagnostics[0]); - } - }; - - // in watch mode, eslint will give us the latest file contents - // store the watch callback so we can trigger an update with eslint's content - watchCompilerHost.watchFile = ( - fileName, - callback, - interval, - ): ts.FileWatcher => { - // specifically (and separately) watch the tsconfig file - // this allows us to react to changes in the tsconfig's include/exclude options - let watcher: Watcher | null = null; - if (fileName.includes(tsconfigPath)) { - watcher = watch( - fileName, - { - interval, - }, - extra, - ); - watcher.on('change', path => { - callback(path, ts.FileWatcherEventKind.Changed); - }); - } - - const normalizedFileName = path.normalize(fileName); - const watchers = ((): Set => { - let watchers = watchCallbackTrackingMap.get(normalizedFileName); - if (!watchers) { - watchers = new Set(); - watchCallbackTrackingMap.set(normalizedFileName, watchers); - } - return watchers; - })(); - watchers.add(callback); - - return { - close: (): void => { - watchers.delete(callback); - - if (watcher) { - watcher.close(); - } - }, - }; - }; - - // when new files are added in watch mode, we need to tell typescript about those files - // if we don't then typescript will act like they don't exist. - watchCompilerHost.watchDirectory = ( - dirPath, - callback, - recursive, - ): ts.FileWatcher => { - const watcher = watch( - dirPath, - { - depth: recursive ? 0 : undefined, - interval: 250, - }, - extra, - ); - watcher.on('add', path => { - callback(path); - }); - - return { - close: watcher.close, - }; - }; - - // allow files with custom extensions to be included in program (uses internal ts api) - const oldOnDirectoryStructureHostCreate = - watchCompilerHost.onCachedDirectoryStructureHostCreate; - watchCompilerHost.onCachedDirectoryStructureHostCreate = (host): void => { - const oldReadDirectory = host.readDirectory; - host.readDirectory = ( - path, - extensions, - exclude, - include, - depth, - ): string[] => - oldReadDirectory( - path, - !extensions - ? undefined - : extensions.concat(extra.extraFileExtensions), - exclude, - include, - depth, - ); - oldOnDirectoryStructureHostCreate(host); - }; - - // create program - const programWatch = ts.createWatchProgram(watchCompilerHost); - const program = programWatch.getProgram().getProgram(); - - // cache watch program and return current program - knownWatchProgramMap.set(tsconfigPath, programWatch); - results.push(program); - } - - parsedFilesSeen.add(filePath); - return results; -} - -/** - * Create program from single root file. Requires a single tsconfig to be specified. - * @param code The code being linted - * @param filePath The file being linted - * @param extra.tsconfigRootDir The root directory for relative tsconfig paths - * @param extra.projects Provided tsconfig paths - * @returns The program containing just the file being linted and associated library files - */ -export function createProgram( - code: string, - filePath: string, - extra: Extra, -): ts.Program | undefined { - if (!extra.projects || extra.projects.length !== 1) { - return undefined; - } - - const tsconfigPath = getTsconfigPath(extra.projects[0], extra); - - const commandLine = ts.getParsedCommandLineOfConfigFile( - tsconfigPath, - defaultCompilerOptions, - { ...ts.sys, onUnRecoverableConfigFileDiagnostic: () => {} }, - ); - - if (!commandLine) { - return undefined; - } - - const compilerHost = ts.createCompilerHost(commandLine.options, true); - const oldReadFile = compilerHost.readFile; - compilerHost.readFile = (fileName: string): string | undefined => - path.normalize(fileName) === path.normalize(filePath) - ? code - : oldReadFile(fileName); - - return ts.createProgram([filePath], commandLine.options, compilerHost); -} diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/tsconfig.json b/packages/typescript-estree/tests/fixtures/invalidFileErrors/tsconfig.json index de5d69d736c8..9f3d8cab0be7 100644 --- a/packages/typescript-estree/tests/fixtures/invalidFileErrors/tsconfig.json +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/tsconfig.json @@ -1,4 +1,7 @@ { + "compilerOptions": { + "allowJs": true + }, "include": [ "ts/included.ts", "ts/included.tsx", diff --git a/packages/typescript-estree/tests/lib/parse.ts b/packages/typescript-estree/tests/lib/parse.ts index 38b80aafbaaf..21664b7bd418 100644 --- a/packages/typescript-estree/tests/lib/parse.ts +++ b/packages/typescript-estree/tests/lib/parse.ts @@ -1,4 +1,4 @@ -import { join, resolve, relative } from 'path'; +import { join, resolve } from 'path'; import * as parser from '../../src/parser'; import * as astConverter from '../../src/ast-converter'; import { TSESTreeOptions } from '../../src/parser-options'; @@ -75,30 +75,16 @@ describe('parse()', () => { loc: true, }); - expect(spy).toHaveBeenCalledWith( - expect.any(Object), - { - code: 'let foo = bar;', - comment: true, - comments: [], - createDefaultProgram: false, - errorOnTypeScriptSyntacticAndSemanticIssues: false, - errorOnUnknownASTType: false, - extraFileExtensions: [], - jsx: false, - loc: true, - log: loggerFn, - noWatch: false, - preserveNodeMaps: false, - projects: [], - range: true, - strict: false, - tokens: expect.any(Array), - tsconfigRootDir: expect.any(String), - useJSXTextNode: false, - }, - false, - ); + expect(spy).toHaveBeenCalled(); + expect(spy.mock.calls[0][1]).toMatchObject({ + code: 'let foo = bar;', + comment: true, + comments: [], + loc: true, + log: loggerFn, + range: true, + tokens: expect.any(Array), + }); }); }); @@ -258,7 +244,7 @@ describe('parse()', () => { const testParse = (filePath: string) => (): void => { parser.parseAndGenerateServices(code, { ...config, - filePath: relative(process.cwd(), join(PROJECT_DIR, filePath)), + filePath: join(PROJECT_DIR, filePath), }); }; diff --git a/packages/typescript-estree/tests/lib/persistentParse.ts b/packages/typescript-estree/tests/lib/persistentParse.ts index c588570bf732..bb77cefd7f7f 100644 --- a/packages/typescript-estree/tests/lib/persistentParse.ts +++ b/packages/typescript-estree/tests/lib/persistentParse.ts @@ -1,20 +1,20 @@ import fs from 'fs'; import path from 'path'; import tmp from 'tmp'; -import { parseAndGenerateServices } from '../../src/parser'; -import { clearCaches } from '../../src/tsconfig-parser'; +import { clearCaches, parseAndGenerateServices } from '../../src/parser'; const tsConfigExcludeBar = { - include: ['./*.ts'], - exclude: ['./bar.ts'], + include: ['src'], + exclude: ['./src/bar.ts'], }; const tsConfigIncludeAll = { - include: ['./*.ts'], + include: ['src'], exclude: [], }; const CONTENTS = { foo: 'console.log("foo")', bar: 'console.log("bar")', + 'baz/bar': 'console.log("baz bar")', }; const tmpDirs = new Set(); @@ -27,17 +27,20 @@ afterEach(() => { tmpDirs.clear(); }); -function writeTSConfig( - dirName: string, - config: Record, -): void { +function writeTSConfig(dirName: string, config: Record): void { fs.writeFileSync(path.join(dirName, 'tsconfig.json'), JSON.stringify(config)); } -function writeFile(dirName: string, file: 'foo' | 'bar'): void { - fs.writeFileSync(path.join(dirName, `${file}.ts`), CONTENTS[file]); +function writeFile(dirName: string, file: 'foo' | 'bar' | 'baz/bar'): void { + fs.writeFileSync(path.join(dirName, 'src', `${file}.ts`), CONTENTS[file]); +} +function renameFile(dirName: string, src: 'bar', dest: 'baz/bar'): void { + fs.renameSync( + path.join(dirName, 'src', `${src}.ts`), + path.join(dirName, 'src', `${dest}.ts`), + ); } -function setup(tsconfig: Record, writeBar = true): string { +function setup(tsconfig: Record, writeBar = true): string { const tmpDir = tmp.dirSync({ keep: false, unsafeCleanup: true, @@ -46,38 +49,21 @@ function setup(tsconfig: Record, writeBar = true): string { writeTSConfig(tmpDir.name, tsconfig); + fs.mkdirSync(path.join(tmpDir.name, 'src')); + fs.mkdirSync(path.join(tmpDir.name, 'src', 'baz')); writeFile(tmpDir.name, 'foo'); writeBar && writeFile(tmpDir.name, 'bar'); return tmpDir.name; } -function parseFile(filename: 'foo' | 'bar', tmpDir: string): void { +function parseFile(filename: 'foo' | 'bar' | 'baz/bar', tmpDir: string): void { parseAndGenerateServices(CONTENTS.foo, { project: './tsconfig.json', tsconfigRootDir: tmpDir, - filePath: path.join(tmpDir, `${filename}.ts`), - }); -} - -// https://github.com/microsoft/TypeScript/blob/a4bacf3bfaf77213c1ef4ddecaf3689837e20ac5/src/compiler/sys.ts#L46-L50 -enum PollingInterval { - High = 2000, - Medium = 500, - Low = 250, -} -async function runTimer(interval: PollingInterval): Promise { - // would love to use jest fake timers, but ts stores references to the standard timeout functions - // so we can't switch to fake timers on the fly :( - await new Promise((resolve): void => { - setTimeout(resolve, interval); + filePath: path.join(tmpDir, 'src', `${filename}.ts`), }); } -async function waitForChokidar(): Promise { - // wait for chokidar to be ready - // this isn't won't be a problem when running the eslint CLI in watch mode because the init takes a few hundred ms - await runTimer(PollingInterval.Medium); -} describe('persistent lint session', () => { it('parses both files successfully when included', () => { @@ -94,42 +80,64 @@ describe('persistent lint session', () => { expect(() => parseFile('bar', PROJECT_DIR)).toThrow(); }); - it('reacts to changes in the tsconfig', async () => { - const PROJECT_DIR = setup(tsConfigExcludeBar); + it('allows parsing of new files', () => { + const PROJECT_DIR = setup(tsConfigIncludeAll, false); // parse once to: assert the config as correct, and to make sure the program is setup expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); + // bar should throw because it doesn't exist yet expect(() => parseFile('bar', PROJECT_DIR)).toThrow(); - await waitForChokidar(); - - // change the config file so it now includes all files - writeTSConfig(PROJECT_DIR, tsConfigIncludeAll); - - // wait for TS to pick up the change to the config file - await runTimer(PollingInterval.High); + // write a new file and attempt to parse it + writeFile(PROJECT_DIR, 'bar'); + // both files should parse fine now expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).not.toThrow(); }); - it('allows parsing of new files', async () => { + it('allows parsing of deeply nested new files', () => { const PROJECT_DIR = setup(tsConfigIncludeAll, false); // parse once to: assert the config as correct, and to make sure the program is setup expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); // bar should throw because it doesn't exist yet - expect(() => parseFile('bar', PROJECT_DIR)).toThrow(); - - await waitForChokidar(); + expect(() => parseFile('baz/bar', PROJECT_DIR)).toThrow(); // write a new file and attempt to parse it - writeFile(PROJECT_DIR, 'bar'); + writeFile(PROJECT_DIR, 'baz/bar'); - // wait for TS to pick up the new file - await runTimer(PollingInterval.Medium); + // both files should parse fine now + expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); + expect(() => parseFile('baz/bar', PROJECT_DIR)).not.toThrow(); + }); + + it('allows renaming of files', () => { + const PROJECT_DIR = setup(tsConfigIncludeAll, true); + + // parse once to: assert the config as correct, and to make sure the program is setup + expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); + // bar should throw because it doesn't exist yet + expect(() => parseFile('baz/bar', PROJECT_DIR)).toThrow(); + + // write a new file and attempt to parse it + renameFile(PROJECT_DIR, 'bar', 'baz/bar'); // both files should parse fine now + expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); + expect(() => parseFile('baz/bar', PROJECT_DIR)).not.toThrow(); + }); + + it('reacts to changes in the tsconfig', () => { + const PROJECT_DIR = setup(tsConfigExcludeBar); + + // parse once to: assert the config as correct, and to make sure the program is setup + expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); + expect(() => parseFile('bar', PROJECT_DIR)).toThrow(); + + // change the config file so it now includes all files + writeTSConfig(PROJECT_DIR, tsConfigIncludeAll); + expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).not.toThrow(); }); diff --git a/packages/typescript-estree/tests/lib/semanticInfo.ts b/packages/typescript-estree/tests/lib/semanticInfo.ts index 6afffd43029d..ccbc9d905819 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.ts @@ -9,11 +9,11 @@ import { parseCodeAndGenerateServices, } from '../../tools/test-utils'; import { + clearCaches, parseAndGenerateServices, ParseAndGenerateServicesResult, } from '../../src/parser'; import { TSESTree } from '../../src/ts-estree'; -import { clearCaches } from '../../src/tsconfig-parser'; const FIXTURES_DIR = './tests/fixtures/semanticInfo'; const testFiles = glob.sync(`${FIXTURES_DIR}/**/*.src.ts`); @@ -236,7 +236,7 @@ describe('semanticInfo', () => { `function M() { return Base }`, createOptions(''), ), - ).toThrow(/The file does not match your project config: /); + ).toThrow(/The file does not match your project config: estree.ts/); }); it('non-existent project file', () => { From 088a6911d503df3b3ffa96f97f30fb43313f9dee Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sat, 19 Oct 2019 12:10:15 -0700 Subject: [PATCH 07/11] fix(typescript-estree): remove now unneeded dep on chokidar --- packages/typescript-estree/package.json | 1 - yarn.lock | 51 +------------------------ 2 files changed, 2 insertions(+), 50 deletions(-) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 225380a2bd27..d8ca623677f3 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -39,7 +39,6 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "chokidar": "^3.0.2", "debug": "^4.1.1", "glob": "^7.1.4", "is-glob": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index 1817d623b819..888b1ba35fca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1655,14 +1655,6 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.0.tgz#e609350e50a9313b472789b2f14ef35808ee14d6" - integrity sha512-Ozz7l4ixzI7Oxj2+cw+p0tVUt27BpaJ+1+q1TCeANWxHpvyn2+Un+YamBdfKu0uh8xLodGhoa1v7595NhKDAuA== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -1919,11 +1911,6 @@ before-after-hook@^2.0.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== -binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== - bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: version "3.7.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.0.tgz#56a6a886e03f6ae577cffedeb524f8f2450293cf" @@ -1953,7 +1940,7 @@ braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@^3.0.2: +braces@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -2155,21 +2142,6 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chokidar@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.0.2.tgz#0d1cd6d04eb2df0327446188cd13736a3367d681" - integrity sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA== - dependencies: - anymatch "^3.0.1" - braces "^3.0.2" - glob-parent "^5.0.0" - is-binary-path "^2.1.0" - is-glob "^4.0.1" - normalize-path "^3.0.0" - readdirp "^3.1.1" - optionalDependencies: - fsevents "^2.0.6" - chownr@^1.1.1, chownr@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" @@ -3515,11 +3487,6 @@ fsevents@^1.2.7: nan "^2.12.1" node-pre-gyp "^0.12.0" -fsevents@^2.0.6: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a" - integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ== - function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -4126,13 +4093,6 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-binary-path@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -6284,7 +6244,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.5: +picomatch@^2.0.5: version "2.0.7" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== @@ -6642,13 +6602,6 @@ readdir-scoped-modules@^1.0.0: graceful-fs "^4.1.2" once "^1.3.0" -readdirp@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.2.tgz#fa85d2d14d4289920e4671dead96431add2ee78a" - integrity sha512-8rhl0xs2cxfVsqzreYCvs8EwBfn/DhVdqtoLmw19uI3SC5avYX9teCurlErfpPXGmYtMHReGaP2RsLnFvz/lnw== - dependencies: - picomatch "^2.0.4" - realpath-native@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" From 2fc9bd26495da59019a3e9dfe8314dba40c6b0a0 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sat, 19 Oct 2019 15:58:34 -0700 Subject: [PATCH 08/11] fix(typescript-estree): correct semver check range (#1109) --- package.json | 4 ++-- packages/typescript-estree/src/parser.ts | 2 +- yarn.lock | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index c848499ff784..6390641fcae0 100644 --- a/package.json +++ b/package.json @@ -73,13 +73,13 @@ "ts-jest": "^24.0.0", "ts-node": "^8.3.0", "tslint": "^5.19.0", - "typescript": ">=3.2.1 <3.8.0 >3.7.0-dev.0" + "typescript": ">=3.2.1 <3.8.0 || >3.7.0-dev.0" }, "collective": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "resolutions": { - "typescript": "^3.7.0-beta" + "typescript": "^3.7.0-dev.20191018" } } diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index cb56c179a004..c887193365f7 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -16,7 +16,7 @@ import { TSESTree } from './ts-estree'; * This needs to be kept in sync with the top-level README.md in the * typescript-eslint monorepo */ -const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.2.1 <3.8.0 >3.7.0-dev.0'; +const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.2.1 <3.8.0 || >3.7.0-dev.0'; const ACTIVE_TYPESCRIPT_VERSION = ts.version; const isRunningSupportedTypeScriptVersion = semver.satisfies( ACTIVE_TYPESCRIPT_VERSION, diff --git a/yarn.lock b/yarn.lock index 888b1ba35fca..88507646d6d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7715,10 +7715,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@*, "typescript@>=3.2.1 <3.8.0 >3.7.0-dev.0", typescript@^3.7.0-beta: - version "3.7.0-dev.20191015" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191015.tgz#283a99aeb09c91963aa16adcf5cb2fccbea9bdc4" - integrity sha512-Cpfj1n4pEUVKL+jtS0mkZodJffyMmf3Wk/UjyZMGX4fsjK5KBPJf3NUlyXij8I8p1E2CAomdS5NPFrAR+z8pKw== +typescript@*, "typescript@>=3.2.1 <3.8.0 || >3.7.0-dev.0", typescript@^3.7.0-dev.20191018: + version "3.7.0-dev.20191018" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191018.tgz#6b98a655b124ca697364e2d7977c469a2bfede3d" + integrity sha512-Z8KpsytbY5lBMp5cc08VFoO8CgHC6IcbgyiA5vjh7fitkoG0qcem9C354YuiWV4O2+i2gdC7vF8tNUYqO/vUkQ== uglify-js@^3.1.4: version "3.6.0" From 7a8cce6d4c7b3756a0267f57596b7204ca8c2566 Mon Sep 17 00:00:00 2001 From: IU Date: Sun, 20 Oct 2019 09:37:33 +0800 Subject: [PATCH 09/11] fix(typescript-estree): parsing error for vue sfc (#1083) --- .../src/create-program/createWatchProgram.ts | 9 +++--- .../fixtures/vue-sfc/.eslintrc.yml | 3 ++ tests/integration/fixtures/vue-sfc/World.vue | 30 +++++++++++++++++++ .../integration/fixtures/vue-sfc/test.js.snap | 8 +++++ tests/integration/fixtures/vue-sfc/test.sh | 3 ++ .../fixtures/vue-sfc/tsconfig.json | 7 +++-- 6 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 tests/integration/fixtures/vue-sfc/World.vue diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index 4bfde8d2ba87..a0a8eeefb485 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -194,7 +194,6 @@ function getProgramsForProjects( results.push(program); } - parsedFilesSeen.add(filePath); return results; } @@ -216,11 +215,13 @@ function createWatchProgram( // ensure readFile reads the code being linted instead of the copy on disk const oldReadFile = watchCompilerHost.readFile; - watchCompilerHost.readFile = (filePath, encoding): string | undefined => - path.normalize(filePath) === - path.normalize(currentLintOperationState.filePath) + watchCompilerHost.readFile = (filePath, encoding): string | undefined => { + parsedFilesSeen.add(filePath); + return path.normalize(filePath) === + path.normalize(currentLintOperationState.filePath) ? currentLintOperationState.code : oldReadFile(filePath, encoding); + }; // ensure process reports error on failure instead of exiting process immediately watchCompilerHost.onUnRecoverableConfigFileDiagnostic = diagnosticReporter; diff --git a/tests/integration/fixtures/vue-sfc/.eslintrc.yml b/tests/integration/fixtures/vue-sfc/.eslintrc.yml index f20f5baf1746..7b9b183b59bc 100644 --- a/tests/integration/fixtures/vue-sfc/.eslintrc.yml +++ b/tests/integration/fixtures/vue-sfc/.eslintrc.yml @@ -6,6 +6,9 @@ env: es6: true node: true +extends: + plugin:vue/essential + parserOptions: # Local version of @typescript-eslint/parser parser: '@typescript-eslint/parser' diff --git a/tests/integration/fixtures/vue-sfc/World.vue b/tests/integration/fixtures/vue-sfc/World.vue new file mode 100644 index 000000000000..ade2b409a3b6 --- /dev/null +++ b/tests/integration/fixtures/vue-sfc/World.vue @@ -0,0 +1,30 @@ + + + + diff --git a/tests/integration/fixtures/vue-sfc/test.js.snap b/tests/integration/fixtures/vue-sfc/test.js.snap index 49bd30cc3897..df23862a1253 100644 --- a/tests/integration/fixtures/vue-sfc/test.js.snap +++ b/tests/integration/fixtures/vue-sfc/test.js.snap @@ -59,5 +59,13 @@ export default Vue.extend({ ", "warningCount": 0, }, + Object { + "errorCount": 0, + "filePath": "/usr/linked/World.vue", + "fixableErrorCount": 0, + "fixableWarningCount": 0, + "messages": Array [], + "warningCount": 0, + }, ] `; diff --git a/tests/integration/fixtures/vue-sfc/test.sh b/tests/integration/fixtures/vue-sfc/test.sh index ba89362dcd13..b61a140ff5be 100755 --- a/tests/integration/fixtures/vue-sfc/test.sh +++ b/tests/integration/fixtures/vue-sfc/test.sh @@ -14,6 +14,9 @@ npm install $(npm pack /usr/eslint-plugin | tail -1) # Install the latest vue-eslint-parser (this may break us occassionally, but it's probably good to get that feedback early) npm install vue-eslint-parser@latest +# Install the latest eslint-plugin-vue (this may break us occassionally, but it's probably good to get that feedback early) +npm install eslint-plugin-vue@latest + # Run the linting # (the "|| true" helps make sure that we run our tests on failed linting runs as well) npx eslint --format json --output-file /usr/lint-output.json --config /usr/linked/.eslintrc.yml /usr/linked/**/*.vue || true diff --git a/tests/integration/fixtures/vue-sfc/tsconfig.json b/tests/integration/fixtures/vue-sfc/tsconfig.json index 86423f3e4aa2..861b7d99bedf 100644 --- a/tests/integration/fixtures/vue-sfc/tsconfig.json +++ b/tests/integration/fixtures/vue-sfc/tsconfig.json @@ -1,5 +1,8 @@ { "compilerOptions": { "strict": true - } -} \ No newline at end of file + }, + "include": [ + "*.vue" + ] +} From 611dff3f58d3c91fd7d7ab9e07a536ae09f50522 Mon Sep 17 00:00:00 2001 From: zEh- Date: Sun, 20 Oct 2019 20:48:31 +0200 Subject: [PATCH 10/11] docs(eslint-plugin): typo in no-unnecessary-condition.md (#1113) --- packages/eslint-plugin/docs/rules/no-unnecessary-condition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md index ac54dba64ca3..996d1d9aae13 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md @@ -59,6 +59,6 @@ The main downside to using this rule is the need for type information. ## Related To -- ESLint: [no-constant-condition](https://eslint.org/docs/rules/no-constant-condition) - this rule is essentially a stronger versison +- ESLint: [no-constant-condition](https://eslint.org/docs/rules/no-constant-condition) - this rule is essentially a stronger version. - [strict-boolean-expression](./strict-boolean-expressions.md) - a stricter alternative to this rule. From fd39bbd8e973ef7b658740e00928d86af0140113 Mon Sep 17 00:00:00 2001 From: James Henry Date: Mon, 21 Oct 2019 17:01:53 +0000 Subject: [PATCH 11/11] chore: publish v2.5.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ lerna.json | 2 +- packages/eslint-plugin-tslint/CHANGELOG.md | 8 ++++++++ packages/eslint-plugin-tslint/package.json | 6 +++--- packages/eslint-plugin/CHANGELOG.md | 17 +++++++++++++++++ packages/eslint-plugin/package.json | 4 ++-- packages/experimental-utils/CHANGELOG.md | 8 ++++++++ packages/experimental-utils/package.json | 4 ++-- packages/parser/CHANGELOG.md | 8 ++++++++ packages/parser/package.json | 8 ++++---- packages/shared-fixtures/CHANGELOG.md | 8 ++++++++ packages/shared-fixtures/package.json | 2 +- packages/typescript-estree/CHANGELOG.md | 19 +++++++++++++++++++ packages/typescript-estree/package.json | 4 ++-- 14 files changed, 104 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66ff74784f0c..490f45a7b3e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.4.0...v2.5.0) (2019-10-21) + + +### Bug Fixes + +* **eslint-plugin:** [no-magic-numbers] Support negative numbers ([#1072](https://github.com/typescript-eslint/typescript-eslint/issues/1072)) ([0c85ac3](https://github.com/typescript-eslint/typescript-eslint/commit/0c85ac3)) +* **typescript-estree:** correct semver check range ([#1109](https://github.com/typescript-eslint/typescript-eslint/issues/1109)) ([2fc9bd2](https://github.com/typescript-eslint/typescript-eslint/commit/2fc9bd2)) +* **typescript-estree:** handle running out of fs watchers ([#1088](https://github.com/typescript-eslint/typescript-eslint/issues/1088)) ([ec62747](https://github.com/typescript-eslint/typescript-eslint/commit/ec62747)) +* **typescript-estree:** parsing error for vue sfc ([#1083](https://github.com/typescript-eslint/typescript-eslint/issues/1083)) ([7a8cce6](https://github.com/typescript-eslint/typescript-eslint/commit/7a8cce6)) +* **typescript-estree:** remove now unneeded dep on chokidar ([088a691](https://github.com/typescript-eslint/typescript-eslint/commit/088a691)) + + +### Features + +* **eslint-plugin:** Support abstract members in member-ordering rule ([#395](https://github.com/typescript-eslint/typescript-eslint/issues/395)) ([#1004](https://github.com/typescript-eslint/typescript-eslint/issues/1004)) ([5f093ac](https://github.com/typescript-eslint/typescript-eslint/commit/5f093ac)) +* **typescript-estree:** support long running lint without watch ([#1106](https://github.com/typescript-eslint/typescript-eslint/issues/1106)) ([ed5564d](https://github.com/typescript-eslint/typescript-eslint/commit/ed5564d)) + + + + + # [2.4.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.3.3...v2.4.0) (2019-10-14) diff --git a/lerna.json b/lerna.json index f4653f7fb8d0..2a40ae9e906b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.4.0", + "version": "2.5.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index f76756a7a3e0..5128c179c540 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.4.0...v2.5.0) (2019-10-21) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + + + + + # [2.4.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.3.3...v2.4.0) (2019-10-14) diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index 88c3cfe1d722..7e2168b5ff1c 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-tslint", - "version": "2.4.0", + "version": "2.5.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -31,7 +31,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.4.0", + "@typescript-eslint/experimental-utils": "2.5.0", "lodash.memoize": "^4.1.2" }, "peerDependencies": { @@ -41,6 +41,6 @@ }, "devDependencies": { "@types/lodash.memoize": "^4.1.4", - "@typescript-eslint/parser": "2.4.0" + "@typescript-eslint/parser": "2.5.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index e39f75e56170..f40f2624eec6 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.4.0...v2.5.0) (2019-10-21) + + +### Bug Fixes + +* **eslint-plugin:** [no-magic-numbers] Support negative numbers ([#1072](https://github.com/typescript-eslint/typescript-eslint/issues/1072)) ([0c85ac3](https://github.com/typescript-eslint/typescript-eslint/commit/0c85ac3)) + + +### Features + +* **eslint-plugin:** Support abstract members in member-ordering rule ([#395](https://github.com/typescript-eslint/typescript-eslint/issues/395)) ([#1004](https://github.com/typescript-eslint/typescript-eslint/issues/1004)) ([5f093ac](https://github.com/typescript-eslint/typescript-eslint/commit/5f093ac)) +* **typescript-estree:** support long running lint without watch ([#1106](https://github.com/typescript-eslint/typescript-eslint/issues/1106)) ([ed5564d](https://github.com/typescript-eslint/typescript-eslint/commit/ed5564d)) + + + + + # [2.4.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.3.3...v2.4.0) (2019-10-14) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 6633b8f2ca59..cacc21e47137 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "2.4.0", + "version": "2.5.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -40,7 +40,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.4.0", + "@typescript-eslint/experimental-utils": "2.5.0", "eslint-utils": "^1.4.2", "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index f3721e9a3a8d..dd63f064e21c 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.4.0...v2.5.0) (2019-10-21) + +**Note:** Version bump only for package @typescript-eslint/experimental-utils + + + + + # [2.4.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.3.3...v2.4.0) (2019-10-14) diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index d39a9110f7ed..0f26e278fec3 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "2.4.0", + "version": "2.5.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -37,7 +37,7 @@ }, "dependencies": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.4.0", + "@typescript-eslint/typescript-estree": "2.5.0", "eslint-scope": "^5.0.0" }, "peerDependencies": { diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 0017c21cb6a0..1e2d3c4d0f6d 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.4.0...v2.5.0) (2019-10-21) + +**Note:** Version bump only for package @typescript-eslint/parser + + + + + # [2.4.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.3.3...v2.4.0) (2019-10-14) diff --git a/packages/parser/package.json b/packages/parser/package.json index e551b4d86ef6..7e3f5e3ce340 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "2.4.0", + "version": "2.5.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -43,13 +43,13 @@ }, "dependencies": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "2.4.0", - "@typescript-eslint/typescript-estree": "2.4.0", + "@typescript-eslint/experimental-utils": "2.5.0", + "@typescript-eslint/typescript-estree": "2.5.0", "eslint-visitor-keys": "^1.1.0" }, "devDependencies": { "@types/glob": "^7.1.1", - "@typescript-eslint/shared-fixtures": "2.4.0", + "@typescript-eslint/shared-fixtures": "2.5.0", "glob": "^7.1.4" } } diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 945b637ecb1d..3fef3f0ee9a4 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.4.0...v2.5.0) (2019-10-21) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [2.4.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.3.3...v2.4.0) (2019-10-14) diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index a833a2bd0ee9..b482cfdcf92b 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "2.4.0", + "version": "2.5.0", "private": true, "scripts": { "build": "tsc -b tsconfig.build.json", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 0278cba2f3fb..2389fef6c569 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.4.0...v2.5.0) (2019-10-21) + + +### Bug Fixes + +* **typescript-estree:** correct semver check range ([#1109](https://github.com/typescript-eslint/typescript-eslint/issues/1109)) ([2fc9bd2](https://github.com/typescript-eslint/typescript-eslint/commit/2fc9bd2)) +* **typescript-estree:** handle running out of fs watchers ([#1088](https://github.com/typescript-eslint/typescript-eslint/issues/1088)) ([ec62747](https://github.com/typescript-eslint/typescript-eslint/commit/ec62747)) +* **typescript-estree:** parsing error for vue sfc ([#1083](https://github.com/typescript-eslint/typescript-eslint/issues/1083)) ([7a8cce6](https://github.com/typescript-eslint/typescript-eslint/commit/7a8cce6)) +* **typescript-estree:** remove now unneeded dep on chokidar ([088a691](https://github.com/typescript-eslint/typescript-eslint/commit/088a691)) + + +### Features + +* **typescript-estree:** support long running lint without watch ([#1106](https://github.com/typescript-eslint/typescript-eslint/issues/1106)) ([ed5564d](https://github.com/typescript-eslint/typescript-eslint/commit/ed5564d)) + + + + + # [2.4.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.3.3...v2.4.0) (2019-10-14) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index d8ca623677f3..508d8400531d 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "2.4.0", + "version": "2.5.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -57,7 +57,7 @@ "@types/lodash.unescape": "^4.0.4", "@types/semver": "^6.0.1", "@types/tmp": "^0.1.0", - "@typescript-eslint/shared-fixtures": "2.4.0", + "@typescript-eslint/shared-fixtures": "2.5.0", "babel-code-frame": "^6.26.0", "glob": "^7.1.4", "lodash.isplainobject": "4.0.6",