• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import debug from 'debug';
2import path from 'path';
3import { getProgramsForProjects } from './createWatchProgram';
4import { firstDefined } from '../node-utils';
5import { Extra } from '../parser-options';
6import { ASTAndProgram } from './shared';
7
8const log = debug('typescript-eslint:typescript-estree:createProjectProgram');
9
10const DEFAULT_EXTRA_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
11
12function getExtension(fileName: string | undefined): string | null {
13  if (!fileName) {
14    return null;
15  }
16  return fileName.endsWith('.d.ts') ? '.d.ts' : path.extname(fileName);
17}
18
19/**
20 * @param code The code of the file being linted
21 * @param createDefaultProgram True if the default program should be created
22 * @param extra The config object
23 * @returns If found, returns the source file corresponding to the code and the containing program
24 */
25function createProjectProgram(
26  code: string,
27  createDefaultProgram: boolean,
28  extra: Extra,
29): ASTAndProgram | undefined {
30  log('Creating project program for: %s', extra.filePath);
31
32  const astAndProgram = firstDefined(
33    getProgramsForProjects(code, extra.filePath, extra),
34    currentProgram => {
35      const ast = currentProgram.getSourceFile(extra.filePath);
36
37      // working around https://github.com/typescript-eslint/typescript-eslint/issues/1573
38      const expectedExt = getExtension(extra.filePath);
39      const returnedExt = getExtension(ast?.fileName);
40      if (expectedExt !== returnedExt) {
41        return;
42      }
43
44      return ast && { ast, program: currentProgram };
45    },
46  );
47
48  if (!astAndProgram && !createDefaultProgram) {
49    // the file was either not matched within the tsconfig, or the extension wasn't expected
50    const errorLines = [
51      '"parserOptions.project" has been set for @typescript-eslint/parser.',
52      `The file does not match your project config: ${path.relative(
53        extra.tsconfigRootDir || process.cwd(),
54        extra.filePath,
55      )}.`,
56    ];
57    let hasMatchedAnError = false;
58
59    const extraFileExtensions = extra.extraFileExtensions || [];
60
61    extraFileExtensions.forEach(extraExtension => {
62      if (!extraExtension.startsWith('.')) {
63        errorLines.push(
64          `Found unexpected extension "${extraExtension}" specified with the "extraFileExtensions" option. Did you mean ".${extraExtension}"?`,
65        );
66      }
67      if (DEFAULT_EXTRA_FILE_EXTENSIONS.includes(extraExtension)) {
68        errorLines.push(
69          `You unnecessarily included the extension "${extraExtension}" with the "extraFileExtensions" option. This extension is already handled by the parser by default.`,
70        );
71      }
72    });
73
74    const fileExtension = path.extname(extra.filePath);
75    if (!DEFAULT_EXTRA_FILE_EXTENSIONS.includes(fileExtension)) {
76      const nonStandardExt = `The extension for the file (${fileExtension}) is non-standard`;
77      if (extraFileExtensions.length > 0) {
78        if (!extraFileExtensions.includes(fileExtension)) {
79          errorLines.push(
80            `${nonStandardExt}. It should be added to your existing "parserOptions.extraFileExtensions".`,
81          );
82          hasMatchedAnError = true;
83        }
84      } else {
85        errorLines.push(
86          `${nonStandardExt}. You should add "parserOptions.extraFileExtensions" to your config.`,
87        );
88        hasMatchedAnError = true;
89      }
90    }
91
92    if (!hasMatchedAnError) {
93      errorLines.push(
94        'The file must be included in at least one of the projects provided.',
95      );
96    }
97
98    throw new Error(errorLines.join('\n'));
99  }
100
101  return astAndProgram;
102}
103
104export { createProjectProgram };
105