when language service is disabled - build program using only open files (#12809)

This commit is contained in:
Vladimir Matveev 2016-12-13 13:21:32 -08:00 committed by GitHub
parent c71e6cc9ef
commit e68161adfa
6 changed files with 132 additions and 108 deletions

View File

@ -1238,6 +1238,7 @@ namespace ts.projectSystem {
projectService.closeExternalProject(externalProjectName);
checkNumberOfProjects(projectService, { configuredProjects: 0 });
});
it("external project with included config file opened after configured project and then closed", () => {
const file1 = {
path: "/a/b/f1.ts",
@ -1797,6 +1798,49 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { configuredProjects: 0 });
});
it("language service disabled state is updated in external projects", () => {
debugger
const f1 = {
path: "/a/app.js",
content: "var x = 1"
};
const f2 = {
path: "/a/largefile.js",
content: ""
};
const host = createServerHost([f1, f2]);
const originalGetFileSize = host.getFileSize;
host.getFileSize = (filePath: string) =>
filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
const service = createProjectService(host);
const projectFileName = "/a/proj.csproj";
service.openExternalProject({
projectFileName,
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
service.checkNumberOfProjects({ externalProjects: 1 });
assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 1");
service.openExternalProject({
projectFileName,
rootFiles: toExternalFiles([f1.path]),
options: {}
});
service.checkNumberOfProjects({ externalProjects: 1 });
assert.isTrue(service.externalProjects[0].languageServiceEnabled, "language service should be enabled");
service.openExternalProject({
projectFileName,
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
service.checkNumberOfProjects({ externalProjects: 1 });
assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 2");
});
it("language service disabled events are triggered", () => {
const f1 = {
path: "/a/app.js",

View File

@ -75,17 +75,32 @@ namespace ts.server {
getFilesAffectedBy(scriptInfo: ScriptInfo): string[];
onProjectUpdateGraph(): void;
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean;
clear(): void;
}
abstract class AbstractBuilder<T extends BuilderFileInfo> implements Builder {
private fileInfos = createFileMap<T>();
/**
* stores set of files from the project.
* NOTE: this field is created on demand and should not be accessed directly.
* Use 'getFileInfos' instead.
*/
private fileInfos_doNotAccessDirectly: FileMap<T>;
constructor(public readonly project: Project, private ctor: { new (scriptInfo: ScriptInfo, project: Project): T }) {
}
private getFileInfos() {
return this.fileInfos_doNotAccessDirectly || (this.fileInfos_doNotAccessDirectly = createFileMap<T>());
}
public clear() {
// drop the existing list - it will be re-created as necessary
this.fileInfos_doNotAccessDirectly = undefined;
}
protected getFileInfo(path: Path): T {
return this.fileInfos.get(path);
return this.getFileInfos().get(path);
}
protected getOrCreateFileInfo(path: Path): T {
@ -99,19 +114,19 @@ namespace ts.server {
}
protected getFileInfoPaths(): Path[] {
return this.fileInfos.getKeys();
return this.getFileInfos().getKeys();
}
protected setFileInfo(path: Path, info: T) {
this.fileInfos.set(path, info);
this.getFileInfos().set(path, info);
}
protected removeFileInfo(path: Path) {
this.fileInfos.remove(path);
this.getFileInfos().remove(path);
}
protected forEachFileInfo(action: (fileInfo: T) => any) {
this.fileInfos.forEachValue((_path, value) => action(value));
this.getFileInfos().forEachValue((_path, value) => action(value));
}
abstract getFilesAffectedBy(scriptInfo: ScriptInfo): string[];
@ -231,6 +246,11 @@ namespace ts.server {
private projectVersionForDependencyGraph: string;
public clear() {
this.projectVersionForDependencyGraph = undefined;
super.clear();
}
private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): ModuleBuilderFileInfo[] {
if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) {
return [];

View File

@ -126,7 +126,7 @@ namespace ts.server {
}
export interface OpenConfiguredProjectResult {
configFileName?: string;
configFileName?: NormalizedPath;
configFileErrors?: Diagnostic[];
}
@ -659,6 +659,13 @@ namespace ts.server {
// open file in inferred project
(projectsToRemove || (projectsToRemove = [])).push(p);
}
if (!p.languageServiceEnabled) {
// if project language service is disabled then we create a program only for open files.
// this means that project should be marked as dirty to force rebuilding of the program
// on the next request
p.markAsDirty();
}
}
if (projectsToRemove) {
for (const project of projectsToRemove) {
@ -1052,9 +1059,7 @@ namespace ts.server {
project.stopWatchingDirectory();
}
else {
if (!project.languageServiceEnabled) {
project.enableLanguageService();
}
project.enableLanguageService();
this.watchConfigDirectoryForProject(project, projectOptions);
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors);
}
@ -1226,9 +1231,22 @@ namespace ts.server {
}
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean): OpenConfiguredProjectResult {
const { configFileName = undefined, configFileErrors = undefined }: OpenConfiguredProjectResult = this.findContainingExternalProject(fileName)
? {}
: this.openOrUpdateConfiguredProjectForFile(fileName);
let configFileName: NormalizedPath;
let configFileErrors: Diagnostic[];
let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName);
if (!project) {
({ configFileName, configFileErrors } = this.openOrUpdateConfiguredProjectForFile(fileName));
if (configFileName) {
project = this.findConfiguredProjectByProjectName(configFileName);
}
}
if (project && !project.languageServiceEnabled) {
// if project language service is disabled then we create a program only for open files.
// this means that project should be marked as dirty to force rebuilding of the program
// on the next request
project.markAsDirty();
}
// at this point if file is the part of some configured/external project then this project should be created
const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent);
@ -1392,8 +1410,15 @@ namespace ts.server {
let exisingConfigFiles: string[];
if (externalProject) {
if (!tsConfigFiles) {
const compilerOptions = convertCompilerOptions(proj.options);
if (this.exceededTotalSizeLimitForNonTsFiles(compilerOptions, proj.rootFiles, externalFilePropertyReader)) {
externalProject.disableLanguageService();
}
else {
externalProject.enableLanguageService();
}
// external project already exists and not config files were added - update the project and return;
this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, convertCompilerOptions(proj.options), proj.typeAcquisition, proj.options.compileOnSave, /*configFileErrors*/ undefined);
this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, compilerOptions, proj.typeAcquisition, proj.options.compileOnSave, /*configFileErrors*/ undefined);
return;
}
// some config files were added to external project (that previously were not there)

View File

@ -90,87 +90,6 @@ namespace ts.server {
}
}
const emptyResult: any[] = [];
const getEmptyResult = () => emptyResult;
const getUndefined = () => <any>undefined;
const emptyEncodedSemanticClassifications = { spans: emptyResult, endOfLineState: EndOfLineState.None };
export function createNoSemanticFeaturesWrapper(realLanguageService: LanguageService): LanguageService {
return {
cleanupSemanticCache: noop,
getSyntacticDiagnostics: (fileName) =>
fileName ? realLanguageService.getSyntacticDiagnostics(fileName) : emptyResult,
getSemanticDiagnostics: getEmptyResult,
getCompilerOptionsDiagnostics: () =>
realLanguageService.getCompilerOptionsDiagnostics(),
getSyntacticClassifications: (fileName, span) =>
realLanguageService.getSyntacticClassifications(fileName, span),
getEncodedSyntacticClassifications: (fileName, span) =>
realLanguageService.getEncodedSyntacticClassifications(fileName, span),
getSemanticClassifications: getEmptyResult,
getEncodedSemanticClassifications: () =>
emptyEncodedSemanticClassifications,
getCompletionsAtPosition: getUndefined,
findReferences: getEmptyResult,
getCompletionEntryDetails: getUndefined,
getQuickInfoAtPosition: getUndefined,
findRenameLocations: getEmptyResult,
getNameOrDottedNameSpan: (fileName, startPos, endPos) =>
realLanguageService.getNameOrDottedNameSpan(fileName, startPos, endPos),
getBreakpointStatementAtPosition: (fileName, position) =>
realLanguageService.getBreakpointStatementAtPosition(fileName, position),
getBraceMatchingAtPosition: (fileName, position) =>
realLanguageService.getBraceMatchingAtPosition(fileName, position),
getSignatureHelpItems: getUndefined,
getDefinitionAtPosition: getEmptyResult,
getRenameInfo: () => ({
canRename: false,
localizedErrorMessage: getLocaleSpecificMessage(Diagnostics.Language_service_is_disabled),
displayName: undefined,
fullDisplayName: undefined,
kind: undefined,
kindModifiers: undefined,
triggerSpan: undefined
}),
getTypeDefinitionAtPosition: getUndefined,
getReferencesAtPosition: getEmptyResult,
getDocumentHighlights: getEmptyResult,
getOccurrencesAtPosition: getEmptyResult,
getNavigateToItems: getEmptyResult,
getNavigationBarItems: fileName =>
realLanguageService.getNavigationBarItems(fileName),
getNavigationTree: fileName =>
realLanguageService.getNavigationTree(fileName),
getOutliningSpans: fileName =>
realLanguageService.getOutliningSpans(fileName),
getTodoComments: getEmptyResult,
getIndentationAtPosition: (fileName, position, options) =>
realLanguageService.getIndentationAtPosition(fileName, position, options),
getFormattingEditsForRange: (fileName, start, end, options) =>
realLanguageService.getFormattingEditsForRange(fileName, start, end, options),
getFormattingEditsForDocument: (fileName, options) =>
realLanguageService.getFormattingEditsForDocument(fileName, options),
getFormattingEditsAfterKeystroke: (fileName, position, key, options) =>
realLanguageService.getFormattingEditsAfterKeystroke(fileName, position, key, options),
getDocCommentTemplateAtPosition: (fileName, position) =>
realLanguageService.getDocCommentTemplateAtPosition(fileName, position),
isValidBraceCompletionAtPosition: (fileName, position, openingBrace) =>
realLanguageService.isValidBraceCompletionAtPosition(fileName, position, openingBrace),
getEmitOutput: getUndefined,
getProgram: () =>
realLanguageService.getProgram(),
getNonBoundSourceFile: fileName =>
realLanguageService.getNonBoundSourceFile(fileName),
dispose: () =>
realLanguageService.dispose(),
getCompletionEntrySymbol: getUndefined,
getImplementationAtPosition: getEmptyResult,
getSourceFile: fileName =>
realLanguageService.getSourceFile(fileName),
getCodeFixesAtPosition: getEmptyResult
};
}
export abstract class Project {
private rootFiles: ScriptInfo[] = [];
private rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
@ -181,8 +100,6 @@ namespace ts.server {
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
private readonly languageService: LanguageService;
// wrapper over the real language service that will suppress all semantic operations
private readonly noSemanticFeaturesLanguageService: LanguageService;
public languageServiceEnabled = true;
@ -258,7 +175,6 @@ namespace ts.server {
this.lsHost.setCompilationSettings(this.compilerOptions);
this.languageService = ts.createLanguageService(this.lsHost, this.documentRegistry);
this.noSemanticFeaturesLanguageService = createNoSemanticFeaturesWrapper(this.languageService);
if (!languageServiceEnabled) {
this.disableLanguageService();
@ -282,9 +198,7 @@ namespace ts.server {
if (ensureSynchronized) {
this.updateGraph();
}
return this.languageServiceEnabled
? this.languageService
: this.noSemanticFeaturesLanguageService;
return this.languageService;
}
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
@ -373,7 +287,10 @@ namespace ts.server {
const result: string[] = [];
if (this.rootFiles) {
for (const f of this.rootFiles) {
result.push(f.fileName);
if (this.languageServiceEnabled || f.isScriptOpen()) {
// if language service is disabled - process only files that are open
result.push(f.fileName);
}
}
if (this.typingFiles) {
for (const f of this.typingFiles) {
@ -389,6 +306,10 @@ namespace ts.server {
}
getScriptInfos() {
if (!this.languageServiceEnabled) {
// if language service is not enabled - return just root files
return this.rootFiles;
}
return map(this.program.getSourceFiles(), sourceFile => {
const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.path);
if (!scriptInfo) {
@ -529,10 +450,6 @@ namespace ts.server {
* @returns: true if set of files in the project stays the same and false - otherwise.
*/
updateGraph(): boolean {
if (!this.languageServiceEnabled) {
return true;
}
this.lsHost.startRecordingFilesWithChangedResolutions();
let hasChanges = this.updateGraphWorker();
@ -564,6 +481,16 @@ namespace ts.server {
if (this.setTypings(cachedTypings)) {
hasChanges = this.updateGraphWorker() || hasChanges;
}
// update builder only if language service is enabled
// otherwise tell it to drop its internal state
if (this.languageServiceEnabled) {
this.builder.onProjectUpdateGraph();
}
else {
this.builder.clear();
}
if (hasChanges) {
this.projectStructureVersion++;
}
@ -602,7 +529,6 @@ namespace ts.server {
}
}
}
this.builder.onProjectUpdateGraph();
return hasChanges;
}
@ -673,7 +599,8 @@ namespace ts.server {
projectName: this.getProjectName(),
version: this.projectStructureVersion,
isInferred: this.projectKind === ProjectKind.Inferred,
options: this.getCompilerOptions()
options: this.getCompilerOptions(),
languageServiceDisabled: !this.languageServiceEnabled
};
const updatedFileNames = this.updatedFileNames;
this.updatedFileNames = undefined;

View File

@ -904,6 +904,11 @@ namespace ts.server.protocol {
* Current set of compiler options for project
*/
options: ts.CompilerOptions;
/**
* true if project language service is disabled
*/
languageServiceDisabled: boolean;
}
/**

View File

@ -1026,6 +1026,9 @@ namespace ts.server {
if (!project) {
Errors.ThrowNoProject();
}
if (!project.languageServiceEnabled) {
return false;
}
const scriptInfo = project.getScriptInfo(file);
return project.builder.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark));
}