Introduce a --syntaxOnly server mode

1. Disable the LS in all projects
 2. Don't create Program objects
 3. Ignore config files
This commit is contained in:
Andrew Casey 2018-04-03 13:40:48 -07:00
parent e2bd282414
commit 855171bde5
9 changed files with 145 additions and 12 deletions

View File

@ -1314,6 +1314,109 @@ namespace ts.projectSystem {
});
describe("ignoreConfigFiles", () => {
it("external project including config file", () => {
const file1 = {
path: "/a/b/f1.ts",
content: "let x =1;"
};
const config1 = {
path: "/a/b/tsconfig.json",
content: JSON.stringify(
{
compilerOptions: {},
files: ["f1.ts"]
}
)
};
const externalProjectName = "externalproject";
const host = createServerHost([file1, config1]);
const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true });
projectService.openExternalProject({
rootFiles: toExternalFiles([file1.path, config1.path]),
options: {},
projectFileName: externalProjectName
});
checkNumberOfProjects(projectService, { externalProjects: 1 });
const proj = projectService.externalProjects[0];
assert.isDefined(proj);
assert.isTrue(proj.fileExists(file1.path));
});
it("loose file included in config file (openClientFile)", () => {
const file1 = {
path: "/a/b/f1.ts",
content: "let x =1;"
};
const config1 = {
path: "/a/b/tsconfig.json",
content: JSON.stringify(
{
compilerOptions: {},
files: ["f1.ts"]
}
)
};
const host = createServerHost([file1, config1]);
const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true });
projectService.openClientFile(file1.path, file1.content);
checkNumberOfProjects(projectService, { inferredProjects: 1 });
const proj = projectService.inferredProjects[0];
assert.isDefined(proj);
assert.isTrue(proj.fileExists(file1.path));
});
it("loose file included in config file (applyCodeChanges)", () => {
const file1 = {
path: "/a/b/f1.ts",
content: "let x =1;"
};
const config1 = {
path: "/a/b/tsconfig.json",
content: JSON.stringify(
{
compilerOptions: {},
files: ["f1.ts"]
}
)
};
const host = createServerHost([file1, config1]);
const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true });
projectService.applyChangesInOpenFiles([{ fileName: file1.path, content: file1.content }], [], []);
checkNumberOfProjects(projectService, { inferredProjects: 1 });
const proj = projectService.inferredProjects[0];
assert.isDefined(proj);
assert.isTrue(proj.fileExists(file1.path));
});
});
it("disable inferred project", () => {
const file1 = {
path: "/a/b/f1.ts",
content: "let x =1;"
};
const host = createServerHost([file1]);
const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true });
projectService.openClientFile(file1.path, file1.content);
checkNumberOfProjects(projectService, { inferredProjects: 1 });
const proj = projectService.inferredProjects[0];
assert.isDefined(proj);
assert.isFalse(proj.languageServiceEnabled);
});
it("reload regular file after closing", () => {
const f1 = {
path: "/a/b/app.ts",

View File

@ -311,6 +311,7 @@ namespace ts.server {
pluginProbeLocations?: ReadonlyArray<string>;
allowLocalPluginLoads?: boolean;
typesMapLocation?: string;
syntaxOnly?: boolean;
}
function getDetailWatchInfo(watchType: WatchType, project: Project | undefined) {
@ -400,6 +401,8 @@ namespace ts.server {
public readonly allowLocalPluginLoads: boolean;
public readonly typesMapLocation: string | undefined;
public readonly syntaxOnly?: boolean;
/** Tracks projects that we have already sent telemetry for. */
private readonly seenProjects = createMap<true>();
@ -420,6 +423,7 @@ namespace ts.server {
this.pluginProbeLocations = opts.pluginProbeLocations || emptyArray;
this.allowLocalPluginLoads = !!opts.allowLocalPluginLoads;
this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(this.getExecutingFilePath(), "../typesMap.json") : opts.typesMapLocation;
this.syntaxOnly = opts.syntaxOnly;
Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService");
if (this.host.realpath) {
@ -1197,6 +1201,11 @@ namespace ts.server {
private forEachConfigFileLocation(info: ScriptInfo,
action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void,
projectRootPath?: NormalizedPath) {
if (this.syntaxOnly) {
return undefined;
}
let searchPath = asNormalizedPath(getDirectoryPath(info.fileName));
while (!projectRootPath || containsPath(projectRootPath, searchPath, this.currentDirectory, !this.host.useCaseSensitiveFileNames)) {
@ -2004,7 +2013,7 @@ namespace ts.server {
const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent);
let project: ConfiguredProject | ExternalProject = this.findExternalProjetContainingOpenScriptInfo(info);
if (!project) {
if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization
configFileName = this.getConfigFileNameForFile(info, projectRootPath);
if (configFileName) {
project = this.findConfiguredProjectByProjectName(configFileName);
@ -2309,7 +2318,7 @@ namespace ts.server {
for (const file of proj.rootFiles) {
const normalized = toNormalizedPath(file.fileName);
if (getBaseConfigFileName(normalized)) {
if (this.host.fileExists(normalized)) {
if (!this.syntaxOnly && this.host.fileExists(normalized)) {
(tsConfigFiles || (tsConfigFiles = [])).push(normalized);
}
}

View File

@ -131,7 +131,7 @@ namespace ts.server {
// wrapper over the real language service that will suppress all semantic operations
protected languageService: LanguageService;
public languageServiceEnabled = true;
public languageServiceEnabled: boolean;
readonly trace?: (s: string) => void;
readonly realpath?: (path: string) => string;
@ -240,6 +240,8 @@ namespace ts.server {
this.compilerOptions.allowNonTsExtensions = true;
}
this.languageServiceEnabled = !projectService.syntaxOnly;
this.setInternalCompilerOptionsForEmittingJsFiles();
const host = this.projectService.host;
if (this.projectService.logger.loggingEnabled()) {
@ -255,7 +257,7 @@ namespace ts.server {
// Use the current directory as resolution root only if the project created using current directory string
this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory, /*logChangesWhenResolvingModule*/ true);
this.languageService = createLanguageService(this, this.documentRegistry);
this.languageService = createLanguageService(this, this.documentRegistry, projectService.syntaxOnly);
if (lastFileExceededProgramSize) {
this.disableLanguageService(lastFileExceededProgramSize);
}
@ -506,7 +508,7 @@ namespace ts.server {
}
enableLanguageService() {
if (this.languageServiceEnabled) {
if (this.languageServiceEnabled || this.projectService.syntaxOnly) {
return;
}
this.languageServiceEnabled = true;
@ -518,6 +520,7 @@ namespace ts.server {
if (!this.languageServiceEnabled) {
return;
}
Debug.assert(!this.projectService.syntaxOnly);
this.languageService.cleanupSemanticCache();
this.languageServiceEnabled = false;
this.lastFileExceededProgramSize = lastFileExceededProgramSize;
@ -875,10 +878,12 @@ namespace ts.server {
this.dirty = false;
this.resolutionCache.finishCachingPerDirectoryResolution();
Debug.assert(oldProgram === undefined || this.program !== undefined);
// bump up the version if
// - oldProgram is not set - this is a first time updateGraph is called
// - newProgram is different from the old program and structure of the old program was not reused.
const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely));
const hasChanges = this.program && (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely)));
this.hasChangedAutomaticTypeDirectiveNames = false;
if (hasChanges) {
if (oldProgram) {

View File

@ -513,6 +513,7 @@ namespace ts.server {
logger,
canUseEvents: true,
suppressDiagnosticEvents,
syntaxOnly,
globalPlugins,
pluginProbeLocations,
allowLocalPluginLoads,
@ -945,6 +946,7 @@ namespace ts.server {
const useInferredProjectPerProjectRoot = hasArgument("--useInferredProjectPerProjectRoot");
const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition");
const suppressDiagnosticEvents = hasArgument("--suppressDiagnosticEvents");
const syntaxOnly = hasArgument("--syntaxOnly");
const telemetryEnabled = hasArgument(Arguments.EnableTelemetry);
logger.info(`Starting TS Server`);

View File

@ -297,6 +297,7 @@ namespace ts.server {
eventHandler?: ProjectServiceEventHandler;
/** Has no effect if eventHandler is also specified. */
suppressDiagnosticEvents?: boolean;
syntaxOnly?: boolean;
throttleWaitMilliseconds?: number;
globalPlugins?: ReadonlyArray<string>;
@ -359,7 +360,8 @@ namespace ts.server {
suppressDiagnosticEvents: this.suppressDiagnosticEvents,
globalPlugins: opts.globalPlugins,
pluginProbeLocations: opts.pluginProbeLocations,
allowLocalPluginLoads: opts.allowLocalPluginLoads
allowLocalPluginLoads: opts.allowLocalPluginLoads,
syntaxOnly: opts.syntaxOnly,
};
this.projectService = new ProjectService(settings);
this.gcTimer = new GcTimer(this.host, /*delay*/ 7000, this.logger);

View File

@ -1152,8 +1152,10 @@ namespace ts {
};
}
export function createLanguageService(host: LanguageServiceHost,
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService {
export function createLanguageService(
host: LanguageServiceHost,
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()),
syntaxOnly = false): LanguageService {
const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
let program: Program;
@ -1188,6 +1190,8 @@ namespace ts {
}
function synchronizeHostData(): void {
Debug.assert(!syntaxOnly);
// perform fast check if host supports it
if (host.getProjectVersion) {
const hostProjectVersion = host.getProjectVersion();
@ -1363,6 +1367,11 @@ namespace ts {
}
function getProgram(): Program {
if (syntaxOnly) {
Debug.assert(program === undefined);
return undefined;
}
synchronizeHostData();
return program;

View File

@ -1197,7 +1197,7 @@ namespace ts {
this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory());
}
const hostAdapter = new LanguageServiceShimHostAdapter(host);
const languageService = createLanguageService(hostAdapter, this.documentRegistry);
const languageService = createLanguageService(hostAdapter, this.documentRegistry, /*syntaxOnly*/ false);
return new LanguageServiceShimObject(this, host, languageService);
}
catch (err) {

View File

@ -4867,7 +4867,7 @@ declare namespace ts {
function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile;
let disableIncrementalParsing: boolean;
function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile;
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService;
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService;
/**
* Get the path of the default library files (lib.d.ts) as distributed with the typescript
* node package.
@ -7295,6 +7295,7 @@ declare namespace ts.server {
eventHandler?: ProjectServiceEventHandler;
/** Has no effect if eventHandler is also specified. */
suppressDiagnosticEvents?: boolean;
syntaxOnly?: boolean;
throttleWaitMilliseconds?: number;
globalPlugins?: ReadonlyArray<string>;
pluginProbeLocations?: ReadonlyArray<string>;
@ -7864,6 +7865,7 @@ declare namespace ts.server {
pluginProbeLocations?: ReadonlyArray<string>;
allowLocalPluginLoads?: boolean;
typesMapLocation?: string;
syntaxOnly?: boolean;
}
class ProjectService {
readonly typingsCache: TypingsCache;
@ -7930,6 +7932,7 @@ declare namespace ts.server {
readonly pluginProbeLocations: ReadonlyArray<string>;
readonly allowLocalPluginLoads: boolean;
readonly typesMapLocation: string | undefined;
readonly syntaxOnly?: boolean;
/** Tracks projects that we have already sent telemetry for. */
private readonly seenProjects;
constructor(opts: ProjectServiceOptions);

View File

@ -5120,7 +5120,7 @@ declare namespace ts {
function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile;
let disableIncrementalParsing: boolean;
function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile;
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService;
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService;
/**
* Get the path of the default library files (lib.d.ts) as distributed with the typescript
* node package.