mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into sourceMapGenerator
This commit is contained in:
@@ -486,7 +486,7 @@ namespace ts.server {
|
||||
|
||||
private readonly hostConfiguration: HostConfiguration;
|
||||
private safelist: SafeList = defaultTypeSafeList;
|
||||
private legacySafelist: { [key: string]: string } = {};
|
||||
private readonly legacySafelist = createMap<string>();
|
||||
|
||||
private pendingProjectUpdates = createMap<Project>();
|
||||
/* @internal */
|
||||
@@ -509,6 +509,8 @@ namespace ts.server {
|
||||
public readonly globalPlugins: ReadonlyArray<string>;
|
||||
public readonly pluginProbeLocations: ReadonlyArray<string>;
|
||||
public readonly allowLocalPluginLoads: boolean;
|
||||
private currentPluginConfigOverrides: Map<any> | undefined;
|
||||
|
||||
public readonly typesMapLocation: string | undefined;
|
||||
|
||||
public readonly syntaxOnly?: boolean;
|
||||
@@ -561,7 +563,7 @@ namespace ts.server {
|
||||
this.typingsCache = new TypingsCache(this.typingsInstaller);
|
||||
|
||||
this.hostConfiguration = {
|
||||
formatCodeOptions: getDefaultFormatCodeSettings(this.host),
|
||||
formatCodeOptions: getDefaultFormatCodeSettings(this.host.newLine),
|
||||
preferences: emptyOptions,
|
||||
hostInfo: "Unknown host",
|
||||
extraFileExtensions: []
|
||||
@@ -638,14 +640,14 @@ namespace ts.server {
|
||||
this.safelist = raw.typesMap;
|
||||
for (const key in raw.simpleMap) {
|
||||
if (raw.simpleMap.hasOwnProperty(key)) {
|
||||
this.legacySafelist[key] = raw.simpleMap[key].toLowerCase();
|
||||
this.legacySafelist.set(key, raw.simpleMap[key].toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.info(`Error loading types map: ${e}`);
|
||||
this.safelist = defaultTypeSafeList;
|
||||
this.legacySafelist = {};
|
||||
this.legacySafelist.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -833,9 +835,9 @@ namespace ts.server {
|
||||
|
||||
/* @internal */
|
||||
private forEachProject(cb: (project: Project) => void) {
|
||||
this.inferredProjects.forEach(cb);
|
||||
this.configuredProjects.forEach(cb);
|
||||
this.externalProjects.forEach(cb);
|
||||
this.configuredProjects.forEach(cb);
|
||||
this.inferredProjects.forEach(cb);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -865,7 +867,7 @@ namespace ts.server {
|
||||
private doEnsureDefaultProjectForFile(fileName: NormalizedPath): Project {
|
||||
this.ensureProjectStructuresUptoDate();
|
||||
const scriptInfo = this.getScriptInfoForNormalizedPath(fileName);
|
||||
return scriptInfo ? scriptInfo.getDefaultProject() : Errors.ThrowNoProject();
|
||||
return scriptInfo ? scriptInfo.getDefaultProject() : (this.logErrorForScriptInfoNotFound(fileName), Errors.ThrowNoProject());
|
||||
}
|
||||
|
||||
getScriptInfoEnsuringProjectsUptoDate(uncheckedFileName: string) {
|
||||
@@ -918,15 +920,20 @@ namespace ts.server {
|
||||
if (!info) {
|
||||
this.logger.msg(`Error: got watch notification for unknown file: ${fileName}`);
|
||||
}
|
||||
else if (eventKind === FileWatcherEventKind.Deleted) {
|
||||
// File was deleted
|
||||
this.handleDeletedFile(info);
|
||||
}
|
||||
else if (!info.isScriptOpen()) {
|
||||
// file has been changed which might affect the set of referenced files in projects that include
|
||||
// this file and set of inferred projects
|
||||
info.delayReloadNonMixedContentFile();
|
||||
this.delayUpdateProjectGraphs(info.containingProjects);
|
||||
else {
|
||||
if (info.containingProjects) {
|
||||
info.containingProjects.forEach(project => project.resolutionCache.removeResolutionsFromProjectReferenceRedirects(info.path));
|
||||
}
|
||||
if (eventKind === FileWatcherEventKind.Deleted) {
|
||||
// File was deleted
|
||||
this.handleDeletedFile(info);
|
||||
}
|
||||
else if (!info.isScriptOpen()) {
|
||||
// file has been changed which might affect the set of referenced files in projects that include
|
||||
// this file and set of inferred projects
|
||||
info.delayReloadNonMixedContentFile();
|
||||
this.delayUpdateProjectGraphs(info.containingProjects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -957,6 +964,7 @@ namespace ts.server {
|
||||
fileOrDirectory => {
|
||||
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
|
||||
project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
|
||||
if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return;
|
||||
const configFilename = project.getConfigFilePath();
|
||||
|
||||
// If the the added or created file or directory is not supported file name, ignore the file
|
||||
@@ -1029,11 +1037,12 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private removeProject(project: Project) {
|
||||
this.logger.info(`remove project: ${project.getRootFiles().toString()}`);
|
||||
this.logger.info("`remove Project::");
|
||||
project.print();
|
||||
|
||||
project.close();
|
||||
if (Debug.shouldAssert(AssertionLevel.Normal)) {
|
||||
this.filenameToScriptInfo.forEach(info => Debug.assert(!info.isAttached(project)));
|
||||
this.filenameToScriptInfo.forEach(info => Debug.assert(!info.isAttached(project), "Found script Info still attached to project", () => `${project.projectName}: ScriptInfos still attached: ${JSON.stringify(mapDefined(arrayFrom(this.filenameToScriptInfo.values()), info => info.isAttached(project) ? info : undefined))}`));
|
||||
}
|
||||
// Remove the project from pending project updates
|
||||
this.pendingProjectUpdates.delete(project.getProjectName());
|
||||
@@ -1469,19 +1478,9 @@ namespace ts.server {
|
||||
|
||||
const writeProjectFileNames = this.logger.hasLevel(LogLevel.verbose);
|
||||
this.logger.startGroup();
|
||||
let counter = 0;
|
||||
const printProjects = (projects: Project[], counter: number): number => {
|
||||
for (const project of projects) {
|
||||
this.logger.info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`);
|
||||
this.logger.info(project.filesToString(writeProjectFileNames));
|
||||
this.logger.info("-----------------------------------------------");
|
||||
counter++;
|
||||
}
|
||||
return counter;
|
||||
};
|
||||
counter = printProjects(this.externalProjects, counter);
|
||||
counter = printProjects(arrayFrom(this.configuredProjects.values()), counter);
|
||||
printProjects(this.inferredProjects, counter);
|
||||
let counter = printProjectsWithCounter(this.externalProjects, 0);
|
||||
counter = printProjectsWithCounter(arrayFrom(this.configuredProjects.values()), counter);
|
||||
printProjectsWithCounter(this.inferredProjects, counter);
|
||||
|
||||
this.logger.info("Open files: ");
|
||||
this.openFiles.forEach((projectRootPath, path) => {
|
||||
@@ -1737,7 +1736,7 @@ namespace ts.server {
|
||||
project.enableLanguageService();
|
||||
project.watchWildcards(createMapFromTemplate(parsedCommandLine.wildcardDirectories!)); // TODO: GH#18217
|
||||
}
|
||||
project.enablePluginsWithOptions(compilerOptions);
|
||||
project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides);
|
||||
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles());
|
||||
this.updateRootAndOptionsOfNonInferredProject(project, filesToAdd, fileNamePropertyReader, compilerOptions, parsedCommandLine.typeAcquisition!, parsedCommandLine.compileOnSave!); // TODO: GH#18217
|
||||
}
|
||||
@@ -1927,7 +1926,7 @@ namespace ts.server {
|
||||
|
||||
private createInferredProject(currentDirectory: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: NormalizedPath): InferredProject {
|
||||
const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects;
|
||||
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, currentDirectory);
|
||||
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, currentDirectory, this.currentPluginConfigOverrides);
|
||||
if (isSingleInferredProject) {
|
||||
this.inferredProjects.unshift(project);
|
||||
}
|
||||
@@ -1958,6 +1957,12 @@ namespace ts.server {
|
||||
return configProject && configProject.getCompilerOptions().configFile;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
logErrorForScriptInfoNotFound(fileName: string): void {
|
||||
const names = arrayFrom(this.filenameToScriptInfo.entries()).map(([path, scriptInfo]) => ({ path, fileName: scriptInfo.fileName }));
|
||||
this.logger.msg(`Could not find file ${JSON.stringify(fileName)}.\nAll files are: ${JSON.stringify(names)}`, Msg.Err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the projects that contain script info through SymLink
|
||||
* Note that this does not return projects in info.containingProjects
|
||||
@@ -2035,6 +2040,8 @@ namespace ts.server {
|
||||
watchDir,
|
||||
(fileOrDirectory) => {
|
||||
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
|
||||
if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return;
|
||||
|
||||
// Has extension
|
||||
Debug.assert(result.refCount > 0);
|
||||
if (watchDir === fileOrDirectoryPath) {
|
||||
@@ -2102,7 +2109,20 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName: NormalizedPath, currentDirectory: string, scriptKind: ScriptKind | undefined, hasMixedContent: boolean | undefined, hostToQueryFileExistsOn: DirectoryStructureHost | undefined) {
|
||||
return this.getOrCreateScriptInfoWorker(fileName, currentDirectory, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn);
|
||||
if (isRootedDiskPath(fileName) || isDynamicFileName(fileName)) {
|
||||
return this.getOrCreateScriptInfoWorker(fileName, currentDirectory, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn);
|
||||
}
|
||||
|
||||
// This is non rooted path with different current directory than project service current directory
|
||||
// Only paths recognized are open relative file paths
|
||||
const info = this.openFilesWithNonRootedDiskPath.get(this.toCanonicalFileName(fileName));
|
||||
if (info) {
|
||||
return info;
|
||||
}
|
||||
|
||||
// This means triple slash references wont be resolved in dynamic and unsaved files
|
||||
// which is intentional since we dont know what it means to be relative to non disk files
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, currentDirectory: string, fileContent: string | undefined, scriptKind: ScriptKind | undefined, hasMixedContent: boolean | undefined) {
|
||||
@@ -2119,7 +2139,7 @@ namespace ts.server {
|
||||
let info = this.getScriptInfoForPath(path);
|
||||
if (!info) {
|
||||
const isDynamic = isDynamicFileName(fileName);
|
||||
Debug.assert(isRootedDiskPath(fileName) || isDynamic || openedByClient, "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nScript info with non-dynamic relative file name can only be open script info`);
|
||||
Debug.assert(isRootedDiskPath(fileName) || isDynamic || openedByClient, "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nScript info with non-dynamic relative file name can only be open script info or in context of host currentDirectory`);
|
||||
Debug.assert(!isRootedDiskPath(fileName) || this.currentDirectory === currentDirectory || !this.openFilesWithNonRootedDiskPath.has(this.toCanonicalFileName(fileName)), "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nOpen script files with non rooted disk path opened with current directory context cannot have same canonical names`);
|
||||
Debug.assert(!isDynamic || this.currentDirectory === currentDirectory, "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nDynamic files must always have current directory context since containing external project name will always match the script info name.`);
|
||||
// If the file is not opened by client and the file doesnot exist on the disk, return
|
||||
@@ -2132,7 +2152,7 @@ namespace ts.server {
|
||||
if (!openedByClient) {
|
||||
this.watchClosedScriptInfo(info);
|
||||
}
|
||||
else if (!isRootedDiskPath(fileName) && currentDirectory !== this.currentDirectory) {
|
||||
else if (!isRootedDiskPath(fileName) && !isDynamic) {
|
||||
// File that is opened by user but isn't rooted disk path
|
||||
this.openFilesWithNonRootedDiskPath.set(this.toCanonicalFileName(fileName), info);
|
||||
}
|
||||
@@ -2487,17 +2507,14 @@ namespace ts.server {
|
||||
}
|
||||
else {
|
||||
// If the configured project for project reference has more than zero references, keep it alive
|
||||
const resolvedProjectReferences = project.getResolvedProjectReferences();
|
||||
if (resolvedProjectReferences) {
|
||||
for (const ref of resolvedProjectReferences) {
|
||||
if (ref) {
|
||||
const refProject = this.configuredProjects.get(ref.sourceFile.path);
|
||||
if (refProject && refProject.hasOpenRef()) {
|
||||
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
|
||||
}
|
||||
project.forEachResolvedProjectReference(ref => {
|
||||
if (ref) {
|
||||
const refProject = this.configuredProjects.get(ref.sourceFile.path);
|
||||
if (refProject && refProject.hasOpenRef()) {
|
||||
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2721,13 +2738,13 @@ namespace ts.server {
|
||||
if (fileExtensionIs(baseName, "js")) {
|
||||
const inferredTypingName = removeFileExtension(baseName);
|
||||
const cleanedTypingName = removeMinAndVersionNumbers(inferredTypingName);
|
||||
if (this.legacySafelist[cleanedTypingName]) {
|
||||
const typeName = this.legacySafelist.get(cleanedTypingName);
|
||||
if (typeName !== undefined) {
|
||||
this.logger.info(`Excluded '${normalizedNames[i]}' because it matched ${cleanedTypingName} from the legacy safelist`);
|
||||
excludedFiles.push(normalizedNames[i]);
|
||||
// *exclude* it from the project...
|
||||
exclude = true;
|
||||
// ... but *include* it in the list of types to acquire
|
||||
const typeName = this.legacySafelist[cleanedTypingName];
|
||||
// Same best-effort dedupe as above
|
||||
if (typeAcqInclude.indexOf(typeName) < 0) {
|
||||
typeAcqInclude.push(typeName);
|
||||
@@ -2878,6 +2895,16 @@ namespace ts.server {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
configurePlugin(args: protocol.ConfigurePluginRequestArguments) {
|
||||
// For any projects that already have the plugin loaded, configure the plugin
|
||||
this.forEachEnabledProject(project => project.onPluginConfigurationChanged(args.pluginName, args.configuration));
|
||||
|
||||
// Also save the current configuration to pass on to any projects that are yet to be loaded.
|
||||
// If a plugin is configured twice, only the latest configuration will be remembered.
|
||||
this.currentPluginConfigOverrides = this.currentPluginConfigOverrides || createMap();
|
||||
this.currentPluginConfigOverrides.set(args.pluginName, args.configuration);
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -2886,4 +2913,12 @@ namespace ts.server {
|
||||
export function isConfigFile(config: ScriptInfoOrConfig): config is TsConfigSourceFile {
|
||||
return (config as TsConfigSourceFile).kind !== undefined;
|
||||
}
|
||||
|
||||
function printProjectsWithCounter(projects: Project[], counter: number) {
|
||||
for (const project of projects) {
|
||||
project.print(counter);
|
||||
counter++;
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
}
|
||||
|
||||
+104
-92
@@ -72,6 +72,12 @@ namespace ts.server {
|
||||
export interface PluginModule {
|
||||
create(createInfo: PluginCreateInfo): LanguageService;
|
||||
getExternalFiles?(proj: Project): string[];
|
||||
onConfigurationChanged?(config: any): void;
|
||||
}
|
||||
|
||||
export interface PluginModuleWithName {
|
||||
name: string;
|
||||
module: PluginModule;
|
||||
}
|
||||
|
||||
export type PluginModuleFactory = (mod: { typescript: typeof ts }) => PluginModule;
|
||||
@@ -92,7 +98,7 @@ namespace ts.server {
|
||||
private program: Program;
|
||||
private externalFiles: SortedReadonlyArray<string>;
|
||||
private missingFilesMap: Map<FileWatcher>;
|
||||
private plugins: PluginModule[] = [];
|
||||
private plugins: PluginModuleWithName[] = [];
|
||||
|
||||
/*@internal*/
|
||||
/**
|
||||
@@ -198,7 +204,7 @@ namespace ts.server {
|
||||
|
||||
/*@internal*/
|
||||
constructor(
|
||||
/*@internal*/readonly projectName: string,
|
||||
readonly projectName: string,
|
||||
readonly projectKind: ProjectKind,
|
||||
readonly projectService: ProjectService,
|
||||
private documentRegistry: DocumentRegistry,
|
||||
@@ -281,8 +287,8 @@ namespace ts.server {
|
||||
return this.projectStateVersion.toString();
|
||||
}
|
||||
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> {
|
||||
return emptyArray;
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getScriptFileNames() {
|
||||
@@ -368,16 +374,16 @@ namespace ts.server {
|
||||
return !this.isWatchedMissingFile(path) && this.directoryStructureHost.fileExists(file);
|
||||
}
|
||||
|
||||
resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModuleFull[] {
|
||||
return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames);
|
||||
resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference): (ResolvedModuleFull | undefined)[] {
|
||||
return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference);
|
||||
}
|
||||
|
||||
getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined {
|
||||
return this.resolutionCache.getResolvedModuleWithFailedLookupLocationsFromCache(moduleName, containingFile);
|
||||
}
|
||||
|
||||
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
|
||||
return this.resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile);
|
||||
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[] {
|
||||
return this.resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference);
|
||||
}
|
||||
|
||||
directoryExists(path: string): boolean {
|
||||
@@ -548,10 +554,10 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getExternalFiles(): SortedReadonlyArray<string> {
|
||||
return toSortedArray(flatMap(this.plugins, plugin => {
|
||||
if (typeof plugin.getExternalFiles !== "function") return;
|
||||
return sort(flatMap(this.plugins, plugin => {
|
||||
if (typeof plugin.module.getExternalFiles !== "function") return;
|
||||
try {
|
||||
return plugin.getExternalFiles(this);
|
||||
return plugin.module.getExternalFiles(this);
|
||||
}
|
||||
catch (e) {
|
||||
this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`);
|
||||
@@ -583,14 +589,11 @@ namespace ts.server {
|
||||
for (const f of this.program.getSourceFiles()) {
|
||||
this.detachScriptInfoIfNotRoot(f.fileName);
|
||||
}
|
||||
const projectReferences = this.program.getResolvedProjectReferences();
|
||||
if (projectReferences) {
|
||||
for (const ref of projectReferences) {
|
||||
if (ref) {
|
||||
this.detachScriptInfoFromProject(ref.sourceFile.fileName);
|
||||
}
|
||||
this.program.forEachResolvedProjectReference(ref => {
|
||||
if (ref) {
|
||||
this.detachScriptInfoFromProject(ref.sourceFile.fileName);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Release external files
|
||||
forEach(this.externalFiles, externalFile => this.detachScriptInfoIfNotRoot(externalFile));
|
||||
@@ -792,41 +795,6 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
private extractUnresolvedImportsFromSourceFile(file: SourceFile, ambientModules: string[]): ReadonlyArray<string> {
|
||||
const cached = this.cachedUnresolvedImportsPerFile.get(file.path);
|
||||
if (cached) {
|
||||
// found cached result, return
|
||||
return cached;
|
||||
}
|
||||
let unresolvedImports: string[] | undefined;
|
||||
if (file.resolvedModules) {
|
||||
file.resolvedModules.forEach((resolvedModule, name) => {
|
||||
// pick unresolved non-relative names
|
||||
if (!resolvedModule && !isExternalModuleNameRelative(name) && !isAmbientlyDeclaredModule(name)) {
|
||||
// for non-scoped names extract part up-to the first slash
|
||||
// for scoped names - extract up to the second slash
|
||||
let trimmed = name.trim();
|
||||
let i = trimmed.indexOf("/");
|
||||
if (i !== -1 && trimmed.charCodeAt(0) === CharacterCodes.at) {
|
||||
i = trimmed.indexOf("/", i + 1);
|
||||
}
|
||||
if (i !== -1) {
|
||||
trimmed = trimmed.substr(0, i);
|
||||
}
|
||||
(unresolvedImports || (unresolvedImports = [])).push(trimmed);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray);
|
||||
return unresolvedImports || emptyArray;
|
||||
|
||||
function isAmbientlyDeclaredModule(name: string) {
|
||||
return ambientModules.some(m => m === name);
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
onFileAddedOrRemoved() {
|
||||
this.hasAddedorRemovedFiles = true;
|
||||
@@ -860,15 +828,7 @@ namespace ts.server {
|
||||
// (can reuse cached imports for files that were not changed)
|
||||
// 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch
|
||||
if (hasNewProgram || changedFiles.length) {
|
||||
let result: string[] | undefined;
|
||||
const ambientModules = this.program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName()));
|
||||
for (const sourceFile of this.program.getSourceFiles()) {
|
||||
const unResolved = this.extractUnresolvedImportsFromSourceFile(sourceFile, ambientModules);
|
||||
if (unResolved !== emptyArray) {
|
||||
(result || (result = [])).push(...unResolved);
|
||||
}
|
||||
}
|
||||
this.lastCachedUnresolvedImportsList = result ? toDeduplicatedSortedArray(result) : emptyArray;
|
||||
this.lastCachedUnresolvedImportsList = getUnresolvedImports(this.program, this.cachedUnresolvedImportsPerFile);
|
||||
}
|
||||
|
||||
this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasAddedorRemovedFiles);
|
||||
@@ -885,7 +845,7 @@ namespace ts.server {
|
||||
|
||||
/*@internal*/
|
||||
updateTypingFiles(typingFiles: SortedReadonlyArray<string>) {
|
||||
enumerateInsertsAndDeletes(typingFiles, this.typingFiles, getStringComparer(!this.useCaseSensitiveFileNames()),
|
||||
enumerateInsertsAndDeletes<string, string>(typingFiles, this.typingFiles, getStringComparer(!this.useCaseSensitiveFileNames()),
|
||||
/*inserted*/ noop,
|
||||
removed => this.detachScriptInfoFromProject(removed)
|
||||
);
|
||||
@@ -925,12 +885,19 @@ namespace ts.server {
|
||||
if (hasNewProgram) {
|
||||
if (oldProgram) {
|
||||
for (const f of oldProgram.getSourceFiles()) {
|
||||
if (this.program.getSourceFileByPath(f.path)) {
|
||||
continue;
|
||||
const newFile = this.program.getSourceFileByPath(f.resolvedPath);
|
||||
if (!newFile || (f.resolvedPath === f.path && newFile.resolvedPath !== f.path)) {
|
||||
// new program does not contain this file - detach it from the project
|
||||
// - remove resolutions only if the new program doesnt contain source file by the path (not resolvedPath since path is used for resolution)
|
||||
this.detachScriptInfoFromProject(f.fileName, !!this.program.getSourceFileByPath(f.path));
|
||||
}
|
||||
// new program does not contain this file - detach it from the project
|
||||
this.detachScriptInfoFromProject(f.fileName);
|
||||
}
|
||||
|
||||
oldProgram.forEachResolvedProjectReference((resolvedProjectReference, resolvedProjectReferencePath) => {
|
||||
if (resolvedProjectReference && !this.program.getResolvedProjectReferenceByPath(resolvedProjectReferencePath)) {
|
||||
this.detachScriptInfoFromProject(resolvedProjectReference.sourceFile.fileName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update the missing file paths watcher
|
||||
@@ -949,7 +916,7 @@ namespace ts.server {
|
||||
|
||||
const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>;
|
||||
this.externalFiles = this.getExternalFiles();
|
||||
enumerateInsertsAndDeletes(this.externalFiles, oldExternalFiles, getStringComparer(!this.useCaseSensitiveFileNames()),
|
||||
enumerateInsertsAndDeletes<string, string>(this.externalFiles, oldExternalFiles, getStringComparer(!this.useCaseSensitiveFileNames()),
|
||||
// Ensure a ScriptInfo is created for new external files. This is performed indirectly
|
||||
// by the LSHost for files in the program when the program is retrieved above but
|
||||
// the program doesn't contain external files so this must be done explicitly.
|
||||
@@ -964,11 +931,13 @@ namespace ts.server {
|
||||
return hasNewProgram;
|
||||
}
|
||||
|
||||
private detachScriptInfoFromProject(uncheckedFileName: string) {
|
||||
private detachScriptInfoFromProject(uncheckedFileName: string, noRemoveResolution?: boolean) {
|
||||
const scriptInfoToDetach = this.projectService.getScriptInfo(uncheckedFileName);
|
||||
if (scriptInfoToDetach) {
|
||||
scriptInfoToDetach.detachFromProject(this);
|
||||
this.resolutionCache.removeResolutionsOfFile(scriptInfoToDetach.path);
|
||||
if (!noRemoveResolution) {
|
||||
this.resolutionCache.removeResolutionsOfFile(scriptInfoToDetach.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,6 +995,13 @@ namespace ts.server {
|
||||
return strBuilder;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
print(counter?: number) {
|
||||
this.writeLog(`Project '${this.projectName}' (${ProjectKind[this.projectKind]}) ${counter === undefined ? "" : counter}`);
|
||||
this.writeLog(this.filesToString(this.projectService.logger.hasLevel(LogLevel.verbose)));
|
||||
this.writeLog("-----------------------------------------------");
|
||||
}
|
||||
|
||||
setCompilerOptions(compilerOptions: CompilerOptions) {
|
||||
if (compilerOptions) {
|
||||
compilerOptions.allowNonTsExtensions = true;
|
||||
@@ -1105,7 +1081,7 @@ namespace ts.server {
|
||||
this.rootFilesMap.delete(info.path);
|
||||
}
|
||||
|
||||
protected enableGlobalPlugins(options: CompilerOptions) {
|
||||
protected enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
|
||||
const host = this.projectService.host;
|
||||
|
||||
if (!host.require) {
|
||||
@@ -1128,12 +1104,13 @@ namespace ts.server {
|
||||
|
||||
// Provide global: true so plugins can detect why they can't find their config
|
||||
this.projectService.logger.info(`Loading global plugin ${globalPluginName}`);
|
||||
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);
|
||||
|
||||
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths, pluginConfigOverrides);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]) {
|
||||
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined) {
|
||||
this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`);
|
||||
|
||||
const log = (message: string) => {
|
||||
@@ -1143,6 +1120,14 @@ namespace ts.server {
|
||||
const resolvedModule = firstDefined(searchPaths, searchPath =>
|
||||
<PluginModuleFactory | undefined>Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log));
|
||||
if (resolvedModule) {
|
||||
const configurationOverride = pluginConfigOverrides && pluginConfigOverrides.get(pluginConfigEntry.name);
|
||||
if (configurationOverride) {
|
||||
// Preserve the name property since it's immutable
|
||||
const pluginName = pluginConfigEntry.name;
|
||||
pluginConfigEntry = configurationOverride;
|
||||
pluginConfigEntry.name = pluginName;
|
||||
}
|
||||
|
||||
this.enableProxy(resolvedModule, pluginConfigEntry);
|
||||
}
|
||||
else {
|
||||
@@ -1150,11 +1135,6 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
/** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */
|
||||
refreshDiagnostics() {
|
||||
this.projectService.sendProjectsUpdatedInBackgroundEvent();
|
||||
}
|
||||
|
||||
private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) {
|
||||
try {
|
||||
if (typeof pluginModuleFactory !== "function") {
|
||||
@@ -1180,12 +1160,47 @@ namespace ts.server {
|
||||
}
|
||||
this.projectService.logger.info(`Plugin validation succeded`);
|
||||
this.languageService = newLS;
|
||||
this.plugins.push(pluginModule);
|
||||
this.plugins.push({ name: configEntry.name, module: pluginModule });
|
||||
}
|
||||
catch (e) {
|
||||
this.projectService.logger.info(`Plugin activation failed: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
onPluginConfigurationChanged(pluginName: string, configuration: any) {
|
||||
this.plugins.filter(plugin => plugin.name === pluginName).forEach(plugin => {
|
||||
if (plugin.module.onConfigurationChanged) {
|
||||
plugin.module.onConfigurationChanged(configuration);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */
|
||||
refreshDiagnostics() {
|
||||
this.projectService.sendProjectsUpdatedInBackgroundEvent();
|
||||
}
|
||||
}
|
||||
|
||||
function getUnresolvedImports(program: Program, cachedUnresolvedImportsPerFile: Map<ReadonlyArray<string>>): SortedReadonlyArray<string> {
|
||||
const ambientModules = program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName()));
|
||||
return sortAndDeduplicate(flatMap(program.getSourceFiles(), sourceFile =>
|
||||
extractUnresolvedImportsFromSourceFile(sourceFile, ambientModules, cachedUnresolvedImportsPerFile)));
|
||||
}
|
||||
function extractUnresolvedImportsFromSourceFile(file: SourceFile, ambientModules: ReadonlyArray<string>, cachedUnresolvedImportsPerFile: Map<ReadonlyArray<string>>): ReadonlyArray<string> {
|
||||
return getOrUpdate(cachedUnresolvedImportsPerFile, file.path, () => {
|
||||
if (!file.resolvedModules) return emptyArray;
|
||||
let unresolvedImports: string[] | undefined;
|
||||
file.resolvedModules.forEach((resolvedModule, name) => {
|
||||
// pick unresolved non-relative names
|
||||
if ((!resolvedModule || !resolutionExtensionIsTSOrJson(resolvedModule.extension)) &&
|
||||
!isExternalModuleNameRelative(name) &&
|
||||
!ambientModules.some(m => m === name)) {
|
||||
unresolvedImports = append(unresolvedImports, parsePackageName(name).packageName);
|
||||
}
|
||||
});
|
||||
return unresolvedImports || emptyArray;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1241,7 +1256,8 @@ namespace ts.server {
|
||||
documentRegistry: DocumentRegistry,
|
||||
compilerOptions: CompilerOptions,
|
||||
projectRootPath: NormalizedPath | undefined,
|
||||
currentDirectory: string | undefined) {
|
||||
currentDirectory: string | undefined,
|
||||
pluginConfigOverrides: Map<any> | undefined) {
|
||||
super(InferredProject.newName(),
|
||||
ProjectKind.Inferred,
|
||||
projectService,
|
||||
@@ -1257,7 +1273,7 @@ namespace ts.server {
|
||||
if (!projectRootPath && !projectService.useSingleInferredProject) {
|
||||
this.canonicalCurrentDirectory = projectService.toCanonicalFileName(this.currentDirectory);
|
||||
}
|
||||
this.enableGlobalPlugins(this.getCompilerOptions());
|
||||
this.enableGlobalPlugins(this.getCompilerOptions(), pluginConfigOverrides);
|
||||
}
|
||||
|
||||
addRoot(info: ScriptInfo) {
|
||||
@@ -1399,8 +1415,8 @@ namespace ts.server {
|
||||
return asNormalizedPath(this.getProjectName());
|
||||
}
|
||||
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> {
|
||||
return this.projectReferences || emptyArray;
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined {
|
||||
return this.projectReferences;
|
||||
}
|
||||
|
||||
updateReferences(refs: ReadonlyArray<ProjectReference> | undefined) {
|
||||
@@ -1408,17 +1424,13 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
getResolvedProjectReferences() {
|
||||
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined {
|
||||
const program = this.getCurrentProgram();
|
||||
return program && program.getResolvedProjectReferences();
|
||||
}
|
||||
|
||||
enablePlugins() {
|
||||
this.enablePluginsWithOptions(this.getCompilerOptions());
|
||||
return program && program.forEachResolvedProjectReference(cb);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
enablePluginsWithOptions(options: CompilerOptions) {
|
||||
enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
|
||||
const host = this.projectService.host;
|
||||
|
||||
if (!host.require) {
|
||||
@@ -1439,11 +1451,11 @@ namespace ts.server {
|
||||
// Enable tsconfig-specified plugins
|
||||
if (options.plugins) {
|
||||
for (const pluginConfigEntry of options.plugins) {
|
||||
this.enablePlugin(pluginConfigEntry, searchPaths);
|
||||
this.enablePlugin(pluginConfigEntry, searchPaths, pluginConfigOverrides);
|
||||
}
|
||||
}
|
||||
|
||||
this.enableGlobalPlugins(options);
|
||||
this.enableGlobalPlugins(options, pluginConfigOverrides);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+22
-1
@@ -129,6 +129,7 @@ namespace ts.server.protocol {
|
||||
GetEditsForFileRename = "getEditsForFileRename",
|
||||
/* @internal */
|
||||
GetEditsForFileRenameFull = "getEditsForFileRename-full",
|
||||
ConfigurePlugin = "configurePlugin"
|
||||
|
||||
// NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`.
|
||||
}
|
||||
@@ -220,6 +221,11 @@ namespace ts.server.protocol {
|
||||
* Contains message body if success === true.
|
||||
*/
|
||||
body?: any;
|
||||
|
||||
/**
|
||||
* Contains extra information that plugin can include to be passed on
|
||||
*/
|
||||
metadata?: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1023,7 +1029,7 @@ namespace ts.server.protocol {
|
||||
/**
|
||||
* The file locations referencing the symbol.
|
||||
*/
|
||||
refs: ReferencesResponseItem[];
|
||||
refs: ReadonlyArray<ReferencesResponseItem>;
|
||||
|
||||
/**
|
||||
* The name of the symbol.
|
||||
@@ -1375,6 +1381,16 @@ namespace ts.server.protocol {
|
||||
export interface ConfigureResponse extends Response {
|
||||
}
|
||||
|
||||
export interface ConfigurePluginRequestArguments {
|
||||
pluginName: string;
|
||||
configuration: any;
|
||||
}
|
||||
|
||||
export interface ConfigurePluginRequest extends Request {
|
||||
command: CommandTypes.ConfigurePlugin;
|
||||
arguments: ConfigurePluginRequestArguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information found in an "open" request.
|
||||
*/
|
||||
@@ -2390,6 +2406,7 @@ namespace ts.server.protocol {
|
||||
*/
|
||||
export interface DiagnosticEvent extends Event {
|
||||
body?: DiagnosticEventBody;
|
||||
event: DiagnosticEventKind;
|
||||
}
|
||||
|
||||
export interface ConfigFileDiagnosticEventBody {
|
||||
@@ -2509,6 +2526,10 @@ namespace ts.server.protocol {
|
||||
maxFileSize: number;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export type AnyEvent = RequestCompletedEvent | DiagnosticEvent | ConfigFileDiagnosticEvent | ProjectLanguageServiceStateEvent | TelemetryEvent |
|
||||
ProjectsUpdatedInBackgroundEvent | ProjectLoadingStartEvent | ProjectLoadingFinishEvent | SurveyReadyEvent | LargeFileReferencedEvent;
|
||||
|
||||
/**
|
||||
* Arguments for reload request.
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace ts.server {
|
||||
|
||||
/* @internal */
|
||||
export class TextStorage {
|
||||
/*@internal*/
|
||||
version: ScriptInfoVersion;
|
||||
|
||||
/**
|
||||
@@ -438,7 +437,7 @@ namespace ts.server {
|
||||
setOptions(formatSettings: FormatCodeSettings, preferences: protocol.UserPreferences | undefined): void {
|
||||
if (formatSettings) {
|
||||
if (!this.formatSettings) {
|
||||
this.formatSettings = getDefaultFormatCodeSettings(this.host);
|
||||
this.formatSettings = getDefaultFormatCodeSettings(this.host.newLine);
|
||||
assign(this.formatSettings, formatSettings);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -382,7 +382,6 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export class LineIndex {
|
||||
root: LineNode;
|
||||
// set this to true to check each edit for accuracy
|
||||
|
||||
+109
-47
@@ -267,7 +267,7 @@ namespace ts.server {
|
||||
projects: Projects,
|
||||
action: (project: Project, value: T) => ReadonlyArray<U> | U | undefined,
|
||||
): U[] {
|
||||
const outputs = flatMap(isArray(projects) ? projects : projects.projects, project => action(project, defaultValue));
|
||||
const outputs = flatMapToMutable(isArray(projects) ? projects : projects.projects, project => action(project, defaultValue));
|
||||
if (!isArray(projects) && projects.symLinkedProjects) {
|
||||
projects.symLinkedProjects.forEach((projects, path) => {
|
||||
const value = getValue(path as Path);
|
||||
@@ -289,7 +289,6 @@ namespace ts.server {
|
||||
function combineProjectOutputWhileOpeningReferencedProjects<T>(
|
||||
projects: Projects,
|
||||
defaultProject: Project,
|
||||
projectService: ProjectService,
|
||||
action: (project: Project) => ReadonlyArray<T>,
|
||||
getLocation: (t: T) => DocumentPosition,
|
||||
resultsEqual: (a: T, b: T) => boolean,
|
||||
@@ -299,7 +298,6 @@ namespace ts.server {
|
||||
projects,
|
||||
defaultProject,
|
||||
/*initialLocation*/ undefined,
|
||||
projectService,
|
||||
({ project }, tryAddToTodo) => {
|
||||
for (const output of action(project)) {
|
||||
if (!contains(outputs, output, resultsEqual) && !tryAddToTodo(project, getLocation(output))) {
|
||||
@@ -312,17 +310,27 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
function combineProjectOutputForRenameLocations(
|
||||
projects: Projects, defaultProject: Project, initialLocation: DocumentPosition, projectService: ProjectService, findInStrings: boolean, findInComments: boolean
|
||||
projects: Projects,
|
||||
defaultProject: Project,
|
||||
initialLocation: DocumentPosition,
|
||||
findInStrings: boolean,
|
||||
findInComments: boolean
|
||||
): ReadonlyArray<RenameLocation> {
|
||||
const outputs: RenameLocation[] = [];
|
||||
|
||||
combineProjectOutputWorker<DocumentPosition>(projects, defaultProject, initialLocation, projectService, ({ project, location }, tryAddToTodo) => {
|
||||
combineProjectOutputWorker<DocumentPosition>(
|
||||
projects,
|
||||
defaultProject,
|
||||
initialLocation,
|
||||
({ project, location }, tryAddToTodo) => {
|
||||
for (const output of project.getLanguageService().findRenameLocations(location.fileName, location.pos, findInStrings, findInComments) || emptyArray) {
|
||||
if (!contains(outputs, output, documentSpansEqual) && !tryAddToTodo(project, documentSpanLocation(output))) {
|
||||
outputs.push(output);
|
||||
if (!contains(outputs, output, documentSpansEqual) && !tryAddToTodo(project, documentSpanLocation(output))) {
|
||||
outputs.push(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, () => getDefinitionLocation(defaultProject, initialLocation));
|
||||
},
|
||||
() => getDefinitionLocation(defaultProject, initialLocation)
|
||||
);
|
||||
|
||||
return outputs;
|
||||
}
|
||||
@@ -333,31 +341,41 @@ namespace ts.server {
|
||||
return info && { fileName: info.fileName, pos: info.textSpan.start };
|
||||
}
|
||||
|
||||
function combineProjectOutputForReferences(projects: Projects, defaultProject: Project, initialLocation: DocumentPosition, projectService: ProjectService): ReadonlyArray<ReferencedSymbol> {
|
||||
function combineProjectOutputForReferences(
|
||||
projects: Projects,
|
||||
defaultProject: Project,
|
||||
initialLocation: DocumentPosition
|
||||
): ReadonlyArray<ReferencedSymbol> {
|
||||
const outputs: ReferencedSymbol[] = [];
|
||||
|
||||
combineProjectOutputWorker<DocumentPosition>(projects, defaultProject, initialLocation, projectService, ({ project, location }, getMappedLocation) => {
|
||||
combineProjectOutputWorker<DocumentPosition>(
|
||||
projects,
|
||||
defaultProject,
|
||||
initialLocation,
|
||||
({ project, location }, getMappedLocation) => {
|
||||
for (const outputReferencedSymbol of project.getLanguageService().findReferences(location.fileName, location.pos) || emptyArray) {
|
||||
const mappedDefinitionFile = getMappedLocation(project, documentSpanLocation(outputReferencedSymbol.definition));
|
||||
const definition: ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ? outputReferencedSymbol.definition : {
|
||||
...outputReferencedSymbol.definition,
|
||||
textSpan: createTextSpan(mappedDefinitionFile.pos, outputReferencedSymbol.definition.textSpan.length),
|
||||
fileName: mappedDefinitionFile.fileName,
|
||||
};
|
||||
let symbolToAddTo = find(outputs, o => documentSpansEqual(o.definition, definition));
|
||||
if (!symbolToAddTo) {
|
||||
symbolToAddTo = { definition, references: [] };
|
||||
outputs.push(symbolToAddTo);
|
||||
}
|
||||
const mappedDefinitionFile = getMappedLocation(project, documentSpanLocation(outputReferencedSymbol.definition));
|
||||
const definition: ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ? outputReferencedSymbol.definition : {
|
||||
...outputReferencedSymbol.definition,
|
||||
textSpan: createTextSpan(mappedDefinitionFile.pos, outputReferencedSymbol.definition.textSpan.length),
|
||||
fileName: mappedDefinitionFile.fileName,
|
||||
};
|
||||
let symbolToAddTo = find(outputs, o => documentSpansEqual(o.definition, definition));
|
||||
if (!symbolToAddTo) {
|
||||
symbolToAddTo = { definition, references: [] };
|
||||
outputs.push(symbolToAddTo);
|
||||
}
|
||||
|
||||
for (const ref of outputReferencedSymbol.references) {
|
||||
// If it's in a mapped file, that is added to the todo list by `getMappedLocation`.
|
||||
if (!contains(symbolToAddTo.references, ref, documentSpansEqual) && !getMappedLocation(project, documentSpanLocation(ref))) {
|
||||
symbolToAddTo.references.push(ref);
|
||||
for (const ref of outputReferencedSymbol.references) {
|
||||
// If it's in a mapped file, that is added to the todo list by `getMappedLocation`.
|
||||
if (!contains(symbolToAddTo.references, ref, documentSpansEqual) && !getMappedLocation(project, documentSpanLocation(ref))) {
|
||||
symbolToAddTo.references.push(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, () => getDefinitionLocation(defaultProject, initialLocation));
|
||||
},
|
||||
() => getDefinitionLocation(defaultProject, initialLocation)
|
||||
);
|
||||
|
||||
return outputs.filter(o => o.references.length !== 0);
|
||||
}
|
||||
@@ -389,10 +407,10 @@ namespace ts.server {
|
||||
projects: Projects,
|
||||
defaultProject: Project,
|
||||
initialLocation: TLocation,
|
||||
projectService: ProjectService,
|
||||
cb: CombineProjectOutputCallback<TLocation>,
|
||||
getDefinition: (() => DocumentPosition | undefined) | undefined,
|
||||
): void {
|
||||
const projectService = defaultProject.projectService;
|
||||
let toDo: ProjectAndLocation<TLocation>[] | undefined;
|
||||
const seenProjects = createMap<true>();
|
||||
forEachProjectInProjects(projects, initialLocation && initialLocation.fileName, (project, path) => {
|
||||
@@ -576,7 +594,7 @@ namespace ts.server {
|
||||
break;
|
||||
case ProjectLoadingFinishEvent:
|
||||
const { project: finishProject } = event.data;
|
||||
this.event<protocol.ProjectLoadingFinishEventBody>({ projectName: finishProject.getProjectName() }, ProjectLoadingStartEvent);
|
||||
this.event<protocol.ProjectLoadingFinishEventBody>({ projectName: finishProject.getProjectName() }, ProjectLoadingFinishEvent);
|
||||
break;
|
||||
case LargeFileReferencedEvent:
|
||||
const { file, fileSize, maxFileSize } = event.data;
|
||||
@@ -688,7 +706,26 @@ namespace ts.server {
|
||||
success,
|
||||
};
|
||||
if (success) {
|
||||
res.body = info;
|
||||
let metadata: unknown;
|
||||
if (isArray(info)) {
|
||||
res.body = info;
|
||||
metadata = (info as WithMetadata<ReadonlyArray<any>>).metadata;
|
||||
delete (info as WithMetadata<ReadonlyArray<any>>).metadata;
|
||||
}
|
||||
else if (typeof info === "object") {
|
||||
if ((info as WithMetadata<{}>).metadata) {
|
||||
const { metadata: infoMetadata, ...body } = (info as WithMetadata<{}>);
|
||||
res.body = body;
|
||||
metadata = infoMetadata;
|
||||
}
|
||||
else {
|
||||
res.body = info;
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.body = info;
|
||||
}
|
||||
if (metadata) res.metadata = metadata;
|
||||
}
|
||||
else {
|
||||
Debug.assert(info === undefined);
|
||||
@@ -1157,7 +1194,9 @@ namespace ts.server {
|
||||
this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file) :
|
||||
this.projectService.getScriptInfo(args.file);
|
||||
if (!scriptInfo) {
|
||||
return ignoreNoProjectError ? emptyArray : Errors.ThrowNoProject();
|
||||
if (ignoreNoProjectError) return emptyArray;
|
||||
this.projectService.logErrorForScriptInfoNotFound(args.file);
|
||||
return Errors.ThrowNoProject();
|
||||
}
|
||||
projects = scriptInfo.containingProjects;
|
||||
symLinkedProjects = this.projectService.getSymlinkedProjects(scriptInfo);
|
||||
@@ -1165,6 +1204,7 @@ namespace ts.server {
|
||||
// filter handles case when 'projects' is undefined
|
||||
projects = filter(projects, p => p.languageServiceEnabled && !p.isOrphan());
|
||||
if (!ignoreNoProjectError && (!projects || !projects.length) && !symLinkedProjects) {
|
||||
this.projectService.logErrorForScriptInfoNotFound(args.file);
|
||||
return Errors.ThrowNoProject();
|
||||
}
|
||||
return symLinkedProjects ? { projects: projects!, symLinkedProjects } : projects!; // TODO: GH#18217
|
||||
@@ -1186,7 +1226,13 @@ namespace ts.server {
|
||||
const position = this.getPositionInFile(args, file);
|
||||
const projects = this.getProjects(args);
|
||||
|
||||
const locations = combineProjectOutputForRenameLocations(projects, this.getDefaultProject(args), { fileName: args.file, pos: position }, this.projectService, !!args.findInStrings, !!args.findInComments);
|
||||
const locations = combineProjectOutputForRenameLocations(
|
||||
projects,
|
||||
this.getDefaultProject(args),
|
||||
{ fileName: args.file, pos: position },
|
||||
!!args.findInStrings,
|
||||
!!args.findInComments
|
||||
);
|
||||
if (!simplifiedResult) return locations;
|
||||
|
||||
const defaultProject = this.getDefaultProject(args);
|
||||
@@ -1220,7 +1266,11 @@ namespace ts.server {
|
||||
const file = toNormalizedPath(args.file);
|
||||
const projects = this.getProjects(args);
|
||||
const position = this.getPositionInFile(args, file);
|
||||
const references = combineProjectOutputForReferences(projects, this.getDefaultProject(args), { fileName: args.file, pos: position }, this.projectService);
|
||||
const references = combineProjectOutputForReferences(
|
||||
projects,
|
||||
this.getDefaultProject(args),
|
||||
{ fileName: args.file, pos: position },
|
||||
);
|
||||
|
||||
if (simplifiedResult) {
|
||||
const defaultProject = this.getDefaultProject(args);
|
||||
@@ -1230,7 +1280,7 @@ namespace ts.server {
|
||||
const nameSpan = nameInfo && nameInfo.textSpan;
|
||||
const symbolStartOffset = nameSpan ? scriptInfo.positionToLineOffset(nameSpan.start).offset : 0;
|
||||
const symbolName = nameSpan ? scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan)) : "";
|
||||
const refs: protocol.ReferencesResponseItem[] = flatMap(references, referencedSymbol =>
|
||||
const refs: ReadonlyArray<protocol.ReferencesResponseItem> = flatMap(references, referencedSymbol =>
|
||||
referencedSymbol.references.map(({ fileName, textSpan, isWriteAccess, isDefinition }): protocol.ReferencesResponseItem => {
|
||||
const scriptInfo = Debug.assertDefined(this.projectService.getScriptInfo(fileName));
|
||||
const start = scriptInfo.positionToLineOffset(textSpan.start);
|
||||
@@ -1464,7 +1514,7 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
private getCompletions(args: protocol.CompletionsRequestArgs, kind: protocol.CommandTypes.CompletionInfo | protocol.CommandTypes.Completions | protocol.CommandTypes.CompletionsFull): ReadonlyArray<protocol.CompletionEntry> | protocol.CompletionInfo | CompletionInfo | undefined {
|
||||
private getCompletions(args: protocol.CompletionsRequestArgs, kind: protocol.CommandTypes.CompletionInfo | protocol.CommandTypes.Completions | protocol.CommandTypes.CompletionsFull): WithMetadata<ReadonlyArray<protocol.CompletionEntry>> | protocol.CompletionInfo | CompletionInfo | undefined {
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
|
||||
const position = this.getPosition(args, scriptInfo);
|
||||
@@ -1489,7 +1539,10 @@ namespace ts.server {
|
||||
}
|
||||
}).sort((a, b) => compareStringsCaseSensitiveUI(a.name, b.name));
|
||||
|
||||
if (kind === protocol.CommandTypes.Completions) return entries;
|
||||
if (kind === protocol.CommandTypes.Completions) {
|
||||
if (completions.metadata) (entries as WithMetadata<ReadonlyArray<protocol.CompletionEntry>>).metadata = completions.metadata;
|
||||
return entries;
|
||||
}
|
||||
|
||||
const res: protocol.CompletionInfo = {
|
||||
...completions,
|
||||
@@ -1724,7 +1777,6 @@ namespace ts.server {
|
||||
return combineProjectOutputWhileOpeningReferencedProjects<NavigateToItem>(
|
||||
this.getProjects(args),
|
||||
this.getDefaultProject(args),
|
||||
this.projectService,
|
||||
project =>
|
||||
project.getLanguageService().getNavigateToItems(searchValue, maxResultCount, /*fileName*/ undefined, /*excludeDts*/ project.isNonTsProject()),
|
||||
documentSpanLocation,
|
||||
@@ -1908,8 +1960,17 @@ namespace ts.server {
|
||||
return textChanges.map(change => this.mapTextChangeToCodeEdit(change));
|
||||
}
|
||||
|
||||
private mapTextChangeToCodeEdit(change: FileTextChanges): protocol.FileCodeEdits {
|
||||
return mapTextChangesToCodeEdits(change, this.projectService.getScriptInfoOrConfig(change.fileName));
|
||||
private mapTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits {
|
||||
const scriptInfo = this.projectService.getScriptInfoOrConfig(textChanges.fileName);
|
||||
if (!!textChanges.isNewFile === !!scriptInfo) {
|
||||
if (!scriptInfo) { // and !isNewFile
|
||||
this.projectService.logErrorForScriptInfoNotFound(textChanges.fileName);
|
||||
}
|
||||
Debug.fail("Expected isNewFile for (only) new files. " + JSON.stringify({ isNewFile: !!textChanges.isNewFile, hasScriptInfo: !!scriptInfo }));
|
||||
}
|
||||
return scriptInfo
|
||||
? { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEdit(textChange, scriptInfo)) }
|
||||
: convertNewFileTextChangeToCodeEdit(textChanges);
|
||||
}
|
||||
|
||||
private convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit {
|
||||
@@ -1983,6 +2044,10 @@ namespace ts.server {
|
||||
this.updateErrorCheck(next, checkList, delay, /*requireOpen*/ false);
|
||||
}
|
||||
|
||||
private configurePlugin(args: protocol.ConfigurePluginRequestArguments) {
|
||||
this.projectService.configurePlugin(args);
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string) {
|
||||
const name = this.host.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
|
||||
return normalizePath(name);
|
||||
@@ -2304,6 +2369,10 @@ namespace ts.server {
|
||||
[CommandNames.GetEditsForFileRenameFull]: (request: protocol.GetEditsForFileRenameRequest) => {
|
||||
return this.requiredResponse(this.getEditsForFileRename(request.arguments, /*simplifiedResult*/ false));
|
||||
},
|
||||
[CommandNames.ConfigurePlugin]: (request: protocol.ConfigurePluginRequest) => {
|
||||
this.configurePlugin(request.arguments);
|
||||
return this.notRequired();
|
||||
}
|
||||
});
|
||||
|
||||
public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {
|
||||
@@ -2423,13 +2492,6 @@ namespace ts.server {
|
||||
return { file: fileName, start: scriptInfo.positionToLineOffset(textSpan.start), end: scriptInfo.positionToLineOffset(textSpanEnd(textSpan)) };
|
||||
}
|
||||
|
||||
function mapTextChangesToCodeEdits(textChanges: FileTextChanges, scriptInfo: ScriptInfoOrConfig | undefined): protocol.FileCodeEdits {
|
||||
Debug.assert(!!textChanges.isNewFile === !scriptInfo, "Expected isNewFile for (only) new files", () => JSON.stringify({ isNewFile: !!textChanges.isNewFile, hasScriptInfo: !!scriptInfo }));
|
||||
return scriptInfo
|
||||
? { fileName: textChanges.fileName, textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEdit(textChange, scriptInfo)) }
|
||||
: convertNewFileTextChangeToCodeEdit(textChanges);
|
||||
}
|
||||
|
||||
function convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfoOrConfig): protocol.CodeEdit {
|
||||
return { start: positionToLineOffset(scriptInfo, change.span.start), end: positionToLineOffset(scriptInfo, textSpanEnd(change.span)), newText: change.newText };
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>, newTypings: string[]) {
|
||||
const typings = toSortedArray(newTypings);
|
||||
const typings = sort(newTypings);
|
||||
this.perProjectCache.set(projectName, {
|
||||
compilerOptions,
|
||||
typeAcquisition,
|
||||
|
||||
@@ -56,30 +56,6 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultFormatCodeSettings(host: ServerHost): FormatCodeSettings {
|
||||
return {
|
||||
indentSize: 4,
|
||||
tabSize: 4,
|
||||
newLineCharacter: host.newLine || "\n",
|
||||
convertTabsToSpaces: true,
|
||||
indentStyle: IndentStyle.Smart,
|
||||
insertSpaceAfterConstructor: false,
|
||||
insertSpaceAfterCommaDelimiter: true,
|
||||
insertSpaceAfterSemicolonInForStatements: true,
|
||||
insertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
insertSpaceAfterKeywordsInControlFlowStatements: true,
|
||||
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
||||
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
|
||||
insertSpaceBeforeFunctionParenthesis: false,
|
||||
placeOpenBraceOnNewLineForFunctions: false,
|
||||
placeOpenBraceOnNewLineForControlBlocks: false,
|
||||
};
|
||||
}
|
||||
|
||||
export type NormalizedPath = string & { __normalizedPathTag: any };
|
||||
|
||||
export function toNormalizedPath(fileName: string): NormalizedPath {
|
||||
@@ -230,31 +206,13 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
export function toSortedArray(arr: string[]): SortedArray<string>;
|
||||
export function toSortedArray<T>(arr: T[], comparer: Comparer<T>): SortedArray<T>;
|
||||
export function toSortedArray<T>(arr: T[], comparer?: Comparer<T>): SortedArray<T> {
|
||||
arr.sort(comparer);
|
||||
return arr as SortedArray<T>;
|
||||
}
|
||||
|
||||
export function toDeduplicatedSortedArray(arr: string[]): SortedArray<string> {
|
||||
arr.sort();
|
||||
filterMutate(arr, isNonDuplicateInSortedArray);
|
||||
return arr as SortedArray<string>;
|
||||
}
|
||||
function isNonDuplicateInSortedArray<T>(value: T, index: number, array: T[]) {
|
||||
return index === 0 || value !== array[index - 1];
|
||||
}
|
||||
|
||||
const indentStr = "\n ";
|
||||
|
||||
/* @internal */
|
||||
export function indent(str: string): string {
|
||||
return indentStr + str.replace(/\n/g, indentStr);
|
||||
}
|
||||
|
||||
/** Put stringified JSON on the next line, indented. */
|
||||
/* @internal */
|
||||
export function stringifyIndented(json: {}): string {
|
||||
return indentStr + JSON.stringify(json);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user