@typescript-eslint/typescript-estree 8.70.1-alpha.9 → 8.70.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -229,6 +229,13 @@ function checkSyntaxError(tsNode, parent, allowPattern) {
229
229
  }
230
230
  break;
231
231
  }
232
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
233
+ case SyntaxKind.AssertEntry:
234
+ case SyntaxKind.ImportAttribute:
235
+ if (node.value.kind !== SyntaxKind.StringLiteral) {
236
+ throw (0, node_utils_1.createError)(node.value, 'String literal expected.');
237
+ }
238
+ break;
232
239
  case SyntaxKind.ExternalModuleReference:
233
240
  if (node.expression.kind !== SyntaxKind.StringLiteral) {
234
241
  throw (0, node_utils_1.createError)(node.expression, 'String literal expected.');
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.clearProgramCache = void 0;
4
4
  exports.clearCaches = clearCaches;
5
5
  const getWatchProgramsForProjects_1 = require("./create-program/getWatchProgramsForProjects");
6
+ const shared_1 = require("./create-program/shared");
6
7
  const parser_1 = require("./parser");
7
8
  const candidateTSConfigRootDirs_1 = require("./parseSettings/candidateTSConfigRootDirs");
8
9
  const createParseSettings_1 = require("./parseSettings/createParseSettings");
@@ -18,6 +19,7 @@ function clearCaches() {
18
19
  (0, candidateTSConfigRootDirs_1.clearCandidateTSConfigRootDirs)();
19
20
  (0, parser_1.clearDefaultProjectMatchedFiles)();
20
21
  (0, parser_1.clearProgramCache)();
22
+ (0, shared_1.clearRealPathCache)();
21
23
  (0, getWatchProgramsForProjects_1.clearWatchCaches)();
22
24
  (0, createParseSettings_1.clearTSConfigMatchCache)();
23
25
  (0, createParseSettings_1.clearTSServerProjectService)();
package/dist/convert.d.ts CHANGED
@@ -35,7 +35,7 @@ export declare class Converter {
35
35
  constructor(ast: ts.SourceFile, options?: ConverterOptions);
36
36
  private convertBindingNameWithTypeAnnotation;
37
37
  /**
38
- * Coverts body Nodes and add a directive field to StringLiterals
38
+ * Converts body Nodes and add a directive field to StringLiterals
39
39
  * @param nodes of ts.Node
40
40
  * @param parent parentNode
41
41
  * @returns Array of body statements
package/dist/convert.js CHANGED
@@ -155,7 +155,7 @@ class Converter {
155
155
  return id;
156
156
  }
157
157
  /**
158
- * Coverts body Nodes and add a directive field to StringLiterals
158
+ * Converts body Nodes and add a directive field to StringLiterals
159
159
  * @param nodes of ts.Node
160
160
  * @param parent parentNode
161
161
  * @returns Array of body statements
@@ -113,6 +113,13 @@ function updateCachedFileList(tsconfigPath, program) {
113
113
  programFileListCache.set(tsconfigPath, fileList);
114
114
  return fileList;
115
115
  }
116
+ /**
117
+ * Checks a program's known files for a path, including when the program knows
118
+ * the file by a path that resolves to the same file on disk.
119
+ */
120
+ function fileListIncludes(fileList, filePath) {
121
+ return fileList.has(filePath) || fileList.has((0, shared_1.getCanonicalRealPath)(filePath));
122
+ }
116
123
  /**
117
124
  * Calculate project environments using options provided by consumer and paths from config
118
125
  * @param parseSettings Internal settings for parsing the file
@@ -152,7 +159,7 @@ function getWatchProgramsForProjects(parseSettings) {
152
159
  updatedProgram = existingWatch.getProgram().getProgram();
153
160
  fileList = updateCachedFileList(tsconfigPath, updatedProgram);
154
161
  }
155
- if (fileList.has(filePath)) {
162
+ if (fileListIncludes(fileList, filePath)) {
156
163
  log('Found existing program for file. %s', filePath);
157
164
  updatedProgram ??= existingWatch.getProgram().getProgram();
158
165
  // sets parent pointers in source files
@@ -177,7 +184,7 @@ function getWatchProgramsForProjects(parseSettings) {
177
184
  updatedProgram.getTypeChecker();
178
185
  // cache and check the file list
179
186
  const fileList = updateCachedFileList(tsconfigPath[0], updatedProgram);
180
- if (fileList.has(filePath)) {
187
+ if (fileListIncludes(fileList, filePath)) {
181
188
  log('Found updated program for file. %s', filePath);
182
189
  // we can return early because we know this program contains the file
183
190
  return [updatedProgram];
@@ -192,7 +199,7 @@ function getWatchProgramsForProjects(parseSettings) {
192
199
  program.getTypeChecker();
193
200
  // cache and check the file list
194
201
  const fileList = updateCachedFileList(tsconfigPath[0], program);
195
- if (fileList.has(filePath)) {
202
+ if (fileListIncludes(fileList, filePath)) {
196
203
  log('Found program for file. %s', filePath);
197
204
  // we can return early because we know this program contains the file
198
205
  return [program];
@@ -301,7 +308,7 @@ function maybeInvalidateProgram(existingWatch, filePath, tsconfigPath) {
301
308
  // tsconfig change means that the file list more than likely changed, so clear the cache
302
309
  programFileListCache.delete(tsconfigPath);
303
310
  }
304
- let sourceFile = updatedProgram.getSourceFile(filePath);
311
+ let sourceFile = (0, shared_1.getSourceFileFromProgram)(updatedProgram, filePath);
305
312
  if (sourceFile) {
306
313
  return updatedProgram;
307
314
  }
@@ -341,7 +348,7 @@ function maybeInvalidateProgram(existingWatch, filePath, tsconfigPath) {
341
348
  programFileListCache.delete(tsconfigPath);
342
349
  // force the immediate resync
343
350
  updatedProgram = existingWatch.getProgram().getProgram();
344
- sourceFile = updatedProgram.getSourceFile(filePath);
351
+ sourceFile = (0, shared_1.getSourceFileFromProgram)(updatedProgram, filePath);
345
352
  if (sourceFile) {
346
353
  return updatedProgram;
347
354
  }
@@ -371,7 +378,7 @@ function maybeInvalidateProgram(existingWatch, filePath, tsconfigPath) {
371
378
  // deleted files means that the file list _has_ changed, so clear the cache
372
379
  programFileListCache.delete(tsconfigPath);
373
380
  updatedProgram = existingWatch.getProgram().getProgram();
374
- sourceFile = updatedProgram.getSourceFile(filePath);
381
+ sourceFile = (0, shared_1.getSourceFileFromProgram)(updatedProgram, filePath);
375
382
  if (sourceFile) {
376
383
  return updatedProgram;
377
384
  }
@@ -18,6 +18,29 @@ export type CanonicalPath = {
18
18
  export declare function getCanonicalFileName(filePath: string): CanonicalPath;
19
19
  export declare function ensureAbsolutePath(p: string, tsconfigRootDir: string): string;
20
20
  export declare function canonicalDirname(p: CanonicalPath): CanonicalPath;
21
+ export declare function clearRealPathCache(): void;
22
+ export declare function getCanonicalRealPath(filePath: string): CanonicalPath;
23
+ /**
24
+ * Maps the canonical real path of each directory that is only reachable through
25
+ * a symlink to the path it's referred to by.
26
+ *
27
+ * TypeScript de-duplicates directories by real path when it expands a TSConfig's
28
+ * `include`s, so a directory reachable both directly and through a symlink is
29
+ * only visited once. Files under it then exist in the project solely under
30
+ * whichever of the two paths was visited first.
31
+ * https://github.com/typescript-eslint/typescript-eslint/issues/2987
32
+ */
33
+ export declare function createSymlinkedDirectories(fileNames: Iterable<string>): ReadonlyMap<CanonicalPath, string>;
34
+ /**
35
+ * Resolves the path a symlinked directory's files are referred to by, for a path
36
+ * that refers to the same file on disk.
37
+ */
38
+ export declare function getPathToSameFile(symlinkedDirectories: ReadonlyMap<CanonicalPath, string>, filePath: string): string | undefined;
39
+ /**
40
+ * Retrieves a program's source file for a path, including when the program knows
41
+ * the file by a different path that resolves to the same file on disk.
42
+ */
43
+ export declare function getSourceFileFromProgram(program: ts.Program, filePath: string): ts.SourceFile | undefined;
21
44
  export declare function getAstFromProgram(currentProgram: Program, filePath: string): ASTAndDefiniteProgram | undefined;
22
45
  /**
23
46
  * Hash content for compare content.
@@ -41,6 +41,11 @@ exports.createDefaultCompilerOptionsFromExtra = createDefaultCompilerOptionsFrom
41
41
  exports.getCanonicalFileName = getCanonicalFileName;
42
42
  exports.ensureAbsolutePath = ensureAbsolutePath;
43
43
  exports.canonicalDirname = canonicalDirname;
44
+ exports.clearRealPathCache = clearRealPathCache;
45
+ exports.getCanonicalRealPath = getCanonicalRealPath;
46
+ exports.createSymlinkedDirectories = createSymlinkedDirectories;
47
+ exports.getPathToSameFile = getPathToSameFile;
48
+ exports.getSourceFileFromProgram = getSourceFileFromProgram;
44
49
  exports.getAstFromProgram = getAstFromProgram;
45
50
  exports.createHash = createHash;
46
51
  const tsconfig_utils_1 = require("@typescript-eslint/tsconfig-utils");
@@ -94,6 +99,85 @@ function ensureAbsolutePath(p, tsconfigRootDir) {
94
99
  function canonicalDirname(p) {
95
100
  return node_path_1.default.dirname(p);
96
101
  }
102
+ const realPathCache = new Map();
103
+ function clearRealPathCache() {
104
+ realPathCache.clear();
105
+ }
106
+ function getRealPath(filePath) {
107
+ let realPath = realPathCache.get(filePath);
108
+ if (realPath == null) {
109
+ // ts.sys doesn't exist in browser environments and isn't required to
110
+ // implement realpath. It returns the given path when it can't resolve one.
111
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
112
+ realPath = ts.sys?.realpath?.(filePath) ?? filePath;
113
+ realPathCache.set(filePath, realPath);
114
+ }
115
+ return realPath;
116
+ }
117
+ function getCanonicalRealPath(filePath) {
118
+ return getCanonicalFileName(getRealPath(filePath));
119
+ }
120
+ /**
121
+ * Maps the canonical real path of each directory that is only reachable through
122
+ * a symlink to the path it's referred to by.
123
+ *
124
+ * TypeScript de-duplicates directories by real path when it expands a TSConfig's
125
+ * `include`s, so a directory reachable both directly and through a symlink is
126
+ * only visited once. Files under it then exist in the project solely under
127
+ * whichever of the two paths was visited first.
128
+ * https://github.com/typescript-eslint/typescript-eslint/issues/2987
129
+ */
130
+ function createSymlinkedDirectories(fileNames) {
131
+ const directories = new Set([...fileNames].map(fileName => node_path_1.default.dirname(fileName)));
132
+ return new Map([...directories]
133
+ .map(directory => [directory, getRealPath(directory)])
134
+ .filter(([directory, realPath]) => directory !== realPath)
135
+ .map(([directory, realPath]) => [
136
+ getCanonicalFileName(realPath),
137
+ directory,
138
+ ]));
139
+ }
140
+ /**
141
+ * Resolves the path a symlinked directory's files are referred to by, for a path
142
+ * that refers to the same file on disk.
143
+ */
144
+ function getPathToSameFile(symlinkedDirectories, filePath) {
145
+ const realPath = getRealPath(filePath);
146
+ const directory = symlinkedDirectories.get(getCanonicalFileName(node_path_1.default.dirname(realPath)));
147
+ return directory == null
148
+ ? undefined
149
+ : node_path_1.default.join(directory, node_path_1.default.basename(realPath));
150
+ }
151
+ const programSymlinkedDirectories = new WeakMap();
152
+ function getSymlinkedDirectories(program) {
153
+ let symlinkedDirectories = programSymlinkedDirectories.get(program);
154
+ if (!symlinkedDirectories) {
155
+ // Only a project's root files can be under a directory it knows by a
156
+ // symlinked path: files pulled in by imports resolve to their real path.
157
+ symlinkedDirectories = createSymlinkedDirectories(program.getRootFileNames());
158
+ programSymlinkedDirectories.set(program, symlinkedDirectories);
159
+ }
160
+ return symlinkedDirectories;
161
+ }
162
+ /**
163
+ * Retrieves a program's source file for a path, including when the program knows
164
+ * the file by a different path that resolves to the same file on disk.
165
+ */
166
+ function getSourceFileFromProgram(program, filePath) {
167
+ const sourceFile = program.getSourceFile(filePath);
168
+ if (sourceFile) {
169
+ return sourceFile;
170
+ }
171
+ const realPath = getRealPath(filePath);
172
+ const fromRealPath = realPath === filePath ? undefined : program.getSourceFile(realPath);
173
+ if (fromRealPath) {
174
+ return fromRealPath;
175
+ }
176
+ const symlinkedFilePath = getPathToSameFile(getSymlinkedDirectories(program), filePath);
177
+ return symlinkedFilePath == null
178
+ ? undefined
179
+ : program.getSourceFile(symlinkedFilePath);
180
+ }
97
181
  const DEFINITION_EXTENSIONS = [
98
182
  ts.Extension.Dts,
99
183
  ts.Extension.Dcts,
@@ -106,7 +190,7 @@ function getExtension(fileName) {
106
190
  return (DEFINITION_EXTENSIONS.find(definitionExt => fileName.endsWith(definitionExt)) ?? node_path_1.default.extname(fileName));
107
191
  }
108
192
  function getAstFromProgram(currentProgram, filePath) {
109
- const ast = currentProgram.getSourceFile(filePath);
193
+ const ast = getSourceFileFromProgram(currentProgram, filePath);
110
194
  // working around https://github.com/typescript-eslint/typescript-eslint/issues/1573
111
195
  const expectedExt = getExtension(filePath);
112
196
  const returnedExt = getExtension(ast?.fileName);
@@ -521,8 +521,16 @@ function convertTokens(ast) {
521
521
  if (isComment(node) || isJSDocComment(node)) {
522
522
  return;
523
523
  }
524
- if (isToken(node) && node.kind !== SyntaxKind.EndOfFileToken) {
525
- result.push(convertToken(node, ast));
524
+ const { kind } = node;
525
+ if (isToken(node) && kind !== SyntaxKind.EndOfFileToken) {
526
+ const token = convertToken(node, ast);
527
+ // TypeScript drops first half of a `<<` in some cases.
528
+ // Gating on `kind` (a cheap check) keeps the rest of the check,
529
+ // which has to look at the text, off the hot path.
530
+ if (kind === SyntaxKind.LessThanToken) {
531
+ addMissingLessThanToken(result, token, ast);
532
+ }
533
+ result.push(token);
526
534
  }
527
535
  else {
528
536
  node.getChildren(ast).forEach(walk);
@@ -531,6 +539,43 @@ function convertTokens(ast) {
531
539
  walk(ast);
532
540
  return result;
533
541
  }
542
+ /**
543
+ * TypeScript recovers the tokens its parser did not keep as children of a node
544
+ * (`<` and `>` of a type argument list, for instance) by re-scanning the source
545
+ * text in the gaps between the children it did keep, and discards any token
546
+ * that runs past the end of the gap being filled.
547
+ *
548
+ * Its scanner produces `<<` as a single token, which the parser splits back
549
+ * apart when the second `<` opens a type parameter list, as in
550
+ * `ReturnType<<T>(x: T) => number>`. The re-scan is not aware of that split,
551
+ * so the `<<` it scans overruns the gap left by the first `<`, and that token
552
+ * is dropped from `getChildren()` entirely.
553
+ *
554
+ * Text skipped in front of a token can otherwise only be trivia, and no trivia
555
+ * can end in a `<`: line comments, shebangs and conflict markers all run to the
556
+ * end of a line, and block comments all end in `*` + `/`. So a `<` sitting
557
+ * directly in front of a token, with anything at all skipped before it,
558
+ * is unambiguously a token TypeScript lost.
559
+ *
560
+ * Only ever called for a `<` token — see the caller.
561
+ */
562
+ function addMissingLessThanToken(result, token, ast) {
563
+ if (result.length === 0) {
564
+ return;
565
+ }
566
+ const currentTokenStart = token.range[0];
567
+ const previousTokenEnd = result[result.length - 1].range[1];
568
+ if (currentTokenStart > previousTokenEnd &&
569
+ ast.text[currentTokenStart - 1] === '<') {
570
+ const range = [currentTokenStart - 1, currentTokenStart];
571
+ result.push({
572
+ type: ts_estree_1.AST_TOKEN_TYPES.Punctuator,
573
+ loc: getLocFor(range, ast),
574
+ range,
575
+ value: '<',
576
+ });
577
+ }
578
+ }
534
579
  class TSError extends Error {
535
580
  fileName;
536
581
  location;
@@ -74,6 +74,10 @@ function openClientFileFromProjectService(defaultProjectMatchedFiles, isDefaultP
74
74
  }
75
75
  }
76
76
  else {
77
+ const fromSymlinkedPath = openFromSymlinkedPath();
78
+ if (fromSymlinkedPath) {
79
+ return fromSymlinkedPath;
80
+ }
77
81
  const wasNotFound = `${parseSettings.filePath} was not found by the project service`;
78
82
  const fileExtension = node_path_1.default.extname(parseSettings.filePath);
79
83
  const extraFileExtensions = parseSettings.extraFileExtensions;
@@ -117,11 +121,31 @@ If you absolutely need more files included, set parserOptions.projectService.max
117
121
  `);
118
122
  }
119
123
  }
120
- return opened;
121
- function openClientFile() {
122
- return serviceAndSettings.service.openClientFile(filePathAbsolute, parseSettings.codeFullText,
124
+ return { filePathAbsolute, opened };
125
+ function openClientFile(fileName = filePathAbsolute) {
126
+ return serviceAndSettings.service.openClientFile(fileName, parseSettings.codeFullText,
123
127
  /* scriptKind */ undefined, parseSettings.tsconfigRootDir);
124
128
  }
129
+ /**
130
+ * A file reachable through a symlink can be in a project only under the
131
+ * symlinked path, in which case the project service doesn't know it by the
132
+ * path it's being linted under.
133
+ * @see https://github.com/typescript-eslint/typescript-eslint/issues/2987
134
+ */
135
+ function openFromSymlinkedPath() {
136
+ if (isDefaultProjectAllowed) {
137
+ return undefined;
138
+ }
139
+ const symlinkedFilePath = getSymlinkedFilePathInProjects(serviceAndSettings.service, filePathAbsolute);
140
+ if (symlinkedFilePath == null) {
141
+ return undefined;
142
+ }
143
+ log('Retrying with the path a project knows the file by: %s', symlinkedFilePath);
144
+ const reopened = openClientFile(symlinkedFilePath);
145
+ return reopened.configFileName
146
+ ? { filePathAbsolute: symlinkedFilePath, opened: reopened }
147
+ : undefined;
148
+ }
125
149
  function openClientFileAndMaybeReload() {
126
150
  log('Opening project service client file at path: %s', filePathAbsolute);
127
151
  let opened = openClientFile();
@@ -142,6 +166,22 @@ If you absolutely need more files included, set parserOptions.projectService.max
142
166
  return opened;
143
167
  }
144
168
  }
169
+ /**
170
+ * Searches the loaded projects for a path that refers to the same file on disk
171
+ * as `filePathAbsolute`, but that a project knows the file by.
172
+ */
173
+ function getSymlinkedFilePathInProjects(service, filePathAbsolute) {
174
+ for (const project of service.configuredProjects.values()) {
175
+ const symlinkedDirectories = (0, shared_1.createSymlinkedDirectories)(project.getFileNames(
176
+ /* excludeFilesFromExternalLibraries */ true,
177
+ /* excludeConfigFiles */ true));
178
+ const symlinkedFilePath = (0, shared_1.getPathToSameFile)(symlinkedDirectories, filePathAbsolute);
179
+ if (symlinkedFilePath != null) {
180
+ return symlinkedFilePath;
181
+ }
182
+ }
183
+ return undefined;
184
+ }
145
185
  function createNoProgramWithProjectService(filePathAbsolute, parseSettings, service) {
146
186
  log('No project service information available. Creating no program.');
147
187
  // If the project service knows about this file, this informs if of changes.
@@ -193,7 +233,7 @@ function useProgramFromProjectService(serviceAndSettings, parseSettings, hasFull
193
233
  const opened = hasFullTypeInformation &&
194
234
  openClientFileFromProjectService(defaultProjectMatchedFiles, isDefaultProjectAllowed, filePathAbsolute, parseSettings, serviceAndSettings);
195
235
  log('Opened project service file: %o', opened);
196
- return retrieveASTAndProgramFor(filePathAbsolute, parseSettings, serviceAndSettings);
236
+ return retrieveASTAndProgramFor(opened ? opened.filePathAbsolute : filePathAbsolute, parseSettings, serviceAndSettings);
197
237
  }
198
238
  function absolutify(filePath, serviceAndSettings) {
199
239
  return node_path_1.default.isAbsolute(filePath)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typescript-eslint/typescript-estree",
3
- "version": "8.70.1-alpha.9",
3
+ "version": "8.70.1",
4
4
  "description": "A parser that converts TypeScript source code into an ESTree compatible form",
5
5
  "files": [
6
6
  "dist",
@@ -43,10 +43,10 @@
43
43
  "syntax"
44
44
  ],
45
45
  "dependencies": {
46
- "@typescript-eslint/project-service": "8.70.1-alpha.9",
47
- "@typescript-eslint/tsconfig-utils": "8.70.1-alpha.9",
48
- "@typescript-eslint/types": "8.70.1-alpha.9",
49
- "@typescript-eslint/visitor-keys": "8.70.1-alpha.9",
46
+ "@typescript-eslint/project-service": "8.70.1",
47
+ "@typescript-eslint/tsconfig-utils": "8.70.1",
48
+ "@typescript-eslint/types": "8.70.1",
49
+ "@typescript-eslint/visitor-keys": "8.70.1",
50
50
  "debug": "^4.4.3",
51
51
  "minimatch": "^10.2.2",
52
52
  "semver": "^7.7.3",