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