@typescript-eslint/typescript-estree
Version:
A parser that converts TypeScript source code into an ESTree compatible form
197 lines (196 loc) • 11.1 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.useProgramFromProjectService = useProgramFromProjectService;
const debug_1 = __importDefault(require("debug"));
const minimatch_1 = require("minimatch");
const node_path_1 = __importDefault(require("node:path"));
const node_util_1 = __importDefault(require("node:util"));
const ts = __importStar(require("typescript"));
const createProjectProgram_1 = require("./create-program/createProjectProgram");
const createSourceFile_1 = require("./create-program/createSourceFile");
const shared_1 = require("./create-program/shared");
const validateDefaultProjectForFilesGlob_1 = require("./create-program/validateDefaultProjectForFilesGlob");
const RELOAD_THROTTLE_MS = 250;
const log = (0, debug_1.default)('typescript-eslint:typescript-estree:useProgramFromProjectService');
const serviceFileExtensions = new WeakMap();
const updateExtraFileExtensions = (service, extraFileExtensions) => {
const currentServiceFileExtensions = serviceFileExtensions.get(service) ?? [];
if (!node_util_1.default.isDeepStrictEqual(currentServiceFileExtensions, extraFileExtensions)) {
log('Updating extra file extensions: before=%s: after=%s', currentServiceFileExtensions, extraFileExtensions);
service.setHostConfiguration({
extraFileExtensions: extraFileExtensions.map(extension => ({
extension,
isMixedContent: false,
scriptKind: ts.ScriptKind.Deferred,
})),
});
serviceFileExtensions.set(service, extraFileExtensions);
log('Extra file extensions updated: %o', extraFileExtensions);
}
};
function openClientFileFromProjectService(defaultProjectMatchedFiles, isDefaultProjectAllowed, filePathAbsolute, parseSettings, serviceSettings) {
const opened = openClientFileAndMaybeReload();
log('Result from attempting to open client file: %o', opened);
log('Default project allowed path: %s, based on config file: %s', isDefaultProjectAllowed, opened.configFileName);
if (opened.configFileName) {
if (isDefaultProjectAllowed) {
throw new Error(`${parseSettings.filePath} was included by allowDefaultProject but also was found in the project service. Consider removing it from allowDefaultProject.`);
}
}
else {
const wasNotFound = `${parseSettings.filePath} was not found by the project service`;
const fileExtension = node_path_1.default.extname(parseSettings.filePath);
const extraFileExtensions = parseSettings.extraFileExtensions;
if (!shared_1.DEFAULT_EXTRA_FILE_EXTENSIONS.has(fileExtension) &&
!extraFileExtensions.includes(fileExtension)) {
const nonStandardExt = `${wasNotFound} because the extension for the file (\`${fileExtension}\`) is non-standard`;
if (extraFileExtensions.length > 0) {
throw new Error(`${nonStandardExt}. It should be added to your existing \`parserOptions.extraFileExtensions\`.`);
}
else {
throw new Error(`${nonStandardExt}. You should add \`parserOptions.extraFileExtensions\` to your config.`);
}
}
if (!isDefaultProjectAllowed) {
throw new Error(`${wasNotFound}. Consider either including it in the tsconfig.json or including it in allowDefaultProject.`);
}
}
// No a configFileName indicates this file wasn't included in a TSConfig.
// That means it must get its type information from the default project.
if (!opened.configFileName) {
defaultProjectMatchedFiles.add(filePathAbsolute);
if (defaultProjectMatchedFiles.size >
serviceSettings.maximumDefaultProjectFileMatchCount) {
const filePrintLimit = 20;
const filesToPrint = [...defaultProjectMatchedFiles].slice(0, filePrintLimit);
const truncatedFileCount = defaultProjectMatchedFiles.size - filesToPrint.length;
throw new Error(`Too many files (>${serviceSettings.maximumDefaultProjectFileMatchCount}) have matched the default project.${validateDefaultProjectForFilesGlob_1.DEFAULT_PROJECT_FILES_ERROR_EXPLANATION}
Matching files:
${filesToPrint.map(file => `- ${file}`).join('\n')}
${truncatedFileCount ? `...and ${truncatedFileCount} more files\n` : ''}
If you absolutely need more files included, set parserOptions.projectService.maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING to a larger value.
`);
}
}
return opened;
function openClientFile() {
return serviceSettings.service.openClientFile(filePathAbsolute, parseSettings.codeFullText,
/* scriptKind */ undefined, parseSettings.tsconfigRootDir);
}
function openClientFileAndMaybeReload() {
log('Opening project service client file at path: %s', filePathAbsolute);
let opened = openClientFile();
// If no project included the file and we're not in single-run mode,
// we might be running in an editor with outdated file info.
// We can try refreshing the project service - debounced for performance.
if (!opened.configFileErrors &&
!opened.configFileName &&
!parseSettings.singleRun &&
!isDefaultProjectAllowed &&
performance.now() - serviceSettings.lastReloadTimestamp >
RELOAD_THROTTLE_MS) {
log('No config file found; reloading project service and retrying.');
serviceSettings.service.reloadProjects();
opened = openClientFile();
serviceSettings.lastReloadTimestamp = performance.now();
}
return opened;
}
}
function createNoProgramWithProjectService(filePathAbsolute, parseSettings, service) {
log('No project service information available. Creating no program.');
// If the project service knows about this file, this informs if of changes.
// Doing so ensures that:
// - if the file is not part of a project, we don't waste time creating a program (fast non-type-aware linting)
// - otherwise, we refresh the file in the project service (moderately fast, since the project is already loaded)
if (service.getScriptInfo(filePathAbsolute)) {
log('Script info available. Opening client file in project service.');
service.openClientFile(filePathAbsolute, parseSettings.codeFullText,
/* scriptKind */ undefined, parseSettings.tsconfigRootDir);
}
return (0, createSourceFile_1.createNoProgram)(parseSettings);
}
function retrieveASTAndProgramFor(filePathAbsolute, parseSettings, serviceSettings) {
log('Retrieving script info and then program for: %s', filePathAbsolute);
const scriptInfo = serviceSettings.service.getScriptInfo(filePathAbsolute);
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const program = serviceSettings.service
.getDefaultProjectForFile(scriptInfo.fileName, true)
.getLanguageService(/*ensureSynchronized*/ true)
.getProgram();
/* eslint-enable @typescript-eslint/no-non-null-assertion */
if (!program) {
log('Could not find project service program for: %s', filePathAbsolute);
return undefined;
}
log('Found project service program for: %s', filePathAbsolute);
return (0, createProjectProgram_1.createProjectProgram)(parseSettings, [program]);
}
function useProgramFromProjectService(serviceSettings, parseSettings, hasFullTypeInformation, defaultProjectMatchedFiles) {
// NOTE: triggers a full project reload when changes are detected
updateExtraFileExtensions(serviceSettings.service, parseSettings.extraFileExtensions);
// We don't canonicalize the filename because it caused a performance regression.
// See https://github.com/typescript-eslint/typescript-eslint/issues/8519
const filePathAbsolute = absolutify(parseSettings.filePath, serviceSettings);
log('Opening project service file for: %s at absolute path %s', parseSettings.filePath, filePathAbsolute);
const filePathRelative = node_path_1.default.relative(parseSettings.tsconfigRootDir, filePathAbsolute);
const isDefaultProjectAllowed = filePathMatchedBy(filePathRelative, serviceSettings.allowDefaultProject);
// Type-aware linting is disabled for this file.
// However, type-aware lint rules might still rely on its contents.
if (!hasFullTypeInformation && !isDefaultProjectAllowed) {
return createNoProgramWithProjectService(filePathAbsolute, parseSettings, serviceSettings.service);
}
// If type info was requested, we attempt to open it in the project service.
// By now, the file is known to be one of:
// - in the project service (valid configuration)
// - allowlisted in the default project (valid configuration)
// - neither, which openClientFileFromProjectService will throw an error for
const opened = hasFullTypeInformation &&
openClientFileFromProjectService(defaultProjectMatchedFiles, isDefaultProjectAllowed, filePathAbsolute, parseSettings, serviceSettings);
log('Opened project service file: %o', opened);
return retrieveASTAndProgramFor(filePathAbsolute, parseSettings, serviceSettings);
}
function absolutify(filePath, serviceSettings) {
return node_path_1.default.isAbsolute(filePath)
? filePath
: node_path_1.default.join(serviceSettings.service.host.getCurrentDirectory(), filePath);
}
function filePathMatchedBy(filePath, allowDefaultProject) {
return !!allowDefaultProject?.some(pattern => (0, minimatch_1.minimatch)(filePath, pattern));
}
;