Skip to content

Commit 7dc9147

Browse files
authored
Merge pull request #23128 from amcasey/SyntaxServerNoProgram
Introduce a --syntaxOnly server mode
2 parents e2bd282 + 855171b commit 7dc9147

File tree

9 files changed

+145
-12
lines changed

9 files changed

+145
-12
lines changed

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,109 @@ namespace ts.projectSystem {
13141314

13151315
});
13161316

1317+
describe("ignoreConfigFiles", () => {
1318+
it("external project including config file", () => {
1319+
const file1 = {
1320+
path: "/a/b/f1.ts",
1321+
content: "let x =1;"
1322+
};
1323+
const config1 = {
1324+
path: "/a/b/tsconfig.json",
1325+
content: JSON.stringify(
1326+
{
1327+
compilerOptions: {},
1328+
files: ["f1.ts"]
1329+
}
1330+
)
1331+
};
1332+
1333+
const externalProjectName = "externalproject";
1334+
const host = createServerHost([file1, config1]);
1335+
const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true });
1336+
projectService.openExternalProject({
1337+
rootFiles: toExternalFiles([file1.path, config1.path]),
1338+
options: {},
1339+
projectFileName: externalProjectName
1340+
});
1341+
1342+
checkNumberOfProjects(projectService, { externalProjects: 1 });
1343+
const proj = projectService.externalProjects[0];
1344+
assert.isDefined(proj);
1345+
1346+
assert.isTrue(proj.fileExists(file1.path));
1347+
});
1348+
1349+
it("loose file included in config file (openClientFile)", () => {
1350+
const file1 = {
1351+
path: "/a/b/f1.ts",
1352+
content: "let x =1;"
1353+
};
1354+
const config1 = {
1355+
path: "/a/b/tsconfig.json",
1356+
content: JSON.stringify(
1357+
{
1358+
compilerOptions: {},
1359+
files: ["f1.ts"]
1360+
}
1361+
)
1362+
};
1363+
1364+
const host = createServerHost([file1, config1]);
1365+
const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true });
1366+
projectService.openClientFile(file1.path, file1.content);
1367+
1368+
checkNumberOfProjects(projectService, { inferredProjects: 1 });
1369+
const proj = projectService.inferredProjects[0];
1370+
assert.isDefined(proj);
1371+
1372+
assert.isTrue(proj.fileExists(file1.path));
1373+
});
1374+
1375+
it("loose file included in config file (applyCodeChanges)", () => {
1376+
const file1 = {
1377+
path: "/a/b/f1.ts",
1378+
content: "let x =1;"
1379+
};
1380+
const config1 = {
1381+
path: "/a/b/tsconfig.json",
1382+
content: JSON.stringify(
1383+
{
1384+
compilerOptions: {},
1385+
files: ["f1.ts"]
1386+
}
1387+
)
1388+
};
1389+
1390+
const host = createServerHost([file1, config1]);
1391+
const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true });
1392+
projectService.applyChangesInOpenFiles([{ fileName: file1.path, content: file1.content }], [], []);
1393+
1394+
checkNumberOfProjects(projectService, { inferredProjects: 1 });
1395+
const proj = projectService.inferredProjects[0];
1396+
assert.isDefined(proj);
1397+
1398+
assert.isTrue(proj.fileExists(file1.path));
1399+
});
1400+
});
1401+
1402+
it("disable inferred project", () => {
1403+
const file1 = {
1404+
path: "/a/b/f1.ts",
1405+
content: "let x =1;"
1406+
};
1407+
1408+
const host = createServerHost([file1]);
1409+
const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true });
1410+
1411+
projectService.openClientFile(file1.path, file1.content);
1412+
1413+
checkNumberOfProjects(projectService, { inferredProjects: 1 });
1414+
const proj = projectService.inferredProjects[0];
1415+
assert.isDefined(proj);
1416+
1417+
assert.isFalse(proj.languageServiceEnabled);
1418+
});
1419+
13171420
it("reload regular file after closing", () => {
13181421
const f1 = {
13191422
path: "/a/b/app.ts",

src/server/editorServices.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ namespace ts.server {
311311
pluginProbeLocations?: ReadonlyArray<string>;
312312
allowLocalPluginLoads?: boolean;
313313
typesMapLocation?: string;
314+
syntaxOnly?: boolean;
314315
}
315316

316317
function getDetailWatchInfo(watchType: WatchType, project: Project | undefined) {
@@ -400,6 +401,8 @@ namespace ts.server {
400401
public readonly allowLocalPluginLoads: boolean;
401402
public readonly typesMapLocation: string | undefined;
402403

404+
public readonly syntaxOnly?: boolean;
405+
403406
/** Tracks projects that we have already sent telemetry for. */
404407
private readonly seenProjects = createMap<true>();
405408

@@ -420,6 +423,7 @@ namespace ts.server {
420423
this.pluginProbeLocations = opts.pluginProbeLocations || emptyArray;
421424
this.allowLocalPluginLoads = !!opts.allowLocalPluginLoads;
422425
this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(this.getExecutingFilePath(), "../typesMap.json") : opts.typesMapLocation;
426+
this.syntaxOnly = opts.syntaxOnly;
423427

424428
Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService");
425429
if (this.host.realpath) {
@@ -1197,6 +1201,11 @@ namespace ts.server {
11971201
private forEachConfigFileLocation(info: ScriptInfo,
11981202
action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void,
11991203
projectRootPath?: NormalizedPath) {
1204+
1205+
if (this.syntaxOnly) {
1206+
return undefined;
1207+
}
1208+
12001209
let searchPath = asNormalizedPath(getDirectoryPath(info.fileName));
12011210

12021211
while (!projectRootPath || containsPath(projectRootPath, searchPath, this.currentDirectory, !this.host.useCaseSensitiveFileNames)) {
@@ -2004,7 +2013,7 @@ namespace ts.server {
20042013

20052014
const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent);
20062015
let project: ConfiguredProject | ExternalProject = this.findExternalProjetContainingOpenScriptInfo(info);
2007-
if (!project) {
2016+
if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization
20082017
configFileName = this.getConfigFileNameForFile(info, projectRootPath);
20092018
if (configFileName) {
20102019
project = this.findConfiguredProjectByProjectName(configFileName);
@@ -2309,7 +2318,7 @@ namespace ts.server {
23092318
for (const file of proj.rootFiles) {
23102319
const normalized = toNormalizedPath(file.fileName);
23112320
if (getBaseConfigFileName(normalized)) {
2312-
if (this.host.fileExists(normalized)) {
2321+
if (!this.syntaxOnly && this.host.fileExists(normalized)) {
23132322
(tsConfigFiles || (tsConfigFiles = [])).push(normalized);
23142323
}
23152324
}

src/server/project.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ namespace ts.server {
131131
// wrapper over the real language service that will suppress all semantic operations
132132
protected languageService: LanguageService;
133133

134-
public languageServiceEnabled = true;
134+
public languageServiceEnabled: boolean;
135135

136136
readonly trace?: (s: string) => void;
137137
readonly realpath?: (path: string) => string;
@@ -240,6 +240,8 @@ namespace ts.server {
240240
this.compilerOptions.allowNonTsExtensions = true;
241241
}
242242

243+
this.languageServiceEnabled = !projectService.syntaxOnly;
244+
243245
this.setInternalCompilerOptionsForEmittingJsFiles();
244246
const host = this.projectService.host;
245247
if (this.projectService.logger.loggingEnabled()) {
@@ -255,7 +257,7 @@ namespace ts.server {
255257

256258
// Use the current directory as resolution root only if the project created using current directory string
257259
this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory, /*logChangesWhenResolvingModule*/ true);
258-
this.languageService = createLanguageService(this, this.documentRegistry);
260+
this.languageService = createLanguageService(this, this.documentRegistry, projectService.syntaxOnly);
259261
if (lastFileExceededProgramSize) {
260262
this.disableLanguageService(lastFileExceededProgramSize);
261263
}
@@ -506,7 +508,7 @@ namespace ts.server {
506508
}
507509

508510
enableLanguageService() {
509-
if (this.languageServiceEnabled) {
511+
if (this.languageServiceEnabled || this.projectService.syntaxOnly) {
510512
return;
511513
}
512514
this.languageServiceEnabled = true;
@@ -518,6 +520,7 @@ namespace ts.server {
518520
if (!this.languageServiceEnabled) {
519521
return;
520522
}
523+
Debug.assert(!this.projectService.syntaxOnly);
521524
this.languageService.cleanupSemanticCache();
522525
this.languageServiceEnabled = false;
523526
this.lastFileExceededProgramSize = lastFileExceededProgramSize;
@@ -875,10 +878,12 @@ namespace ts.server {
875878
this.dirty = false;
876879
this.resolutionCache.finishCachingPerDirectoryResolution();
877880

881+
Debug.assert(oldProgram === undefined || this.program !== undefined);
882+
878883
// bump up the version if
879884
// - oldProgram is not set - this is a first time updateGraph is called
880885
// - newProgram is different from the old program and structure of the old program was not reused.
881-
const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely));
886+
const hasChanges = this.program && (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely)));
882887
this.hasChangedAutomaticTypeDirectiveNames = false;
883888
if (hasChanges) {
884889
if (oldProgram) {

src/server/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ namespace ts.server {
513513
logger,
514514
canUseEvents: true,
515515
suppressDiagnosticEvents,
516+
syntaxOnly,
516517
globalPlugins,
517518
pluginProbeLocations,
518519
allowLocalPluginLoads,
@@ -945,6 +946,7 @@ namespace ts.server {
945946
const useInferredProjectPerProjectRoot = hasArgument("--useInferredProjectPerProjectRoot");
946947
const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition");
947948
const suppressDiagnosticEvents = hasArgument("--suppressDiagnosticEvents");
949+
const syntaxOnly = hasArgument("--syntaxOnly");
948950
const telemetryEnabled = hasArgument(Arguments.EnableTelemetry);
949951

950952
logger.info(`Starting TS Server`);

src/server/session.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ namespace ts.server {
297297
eventHandler?: ProjectServiceEventHandler;
298298
/** Has no effect if eventHandler is also specified. */
299299
suppressDiagnosticEvents?: boolean;
300+
syntaxOnly?: boolean;
300301
throttleWaitMilliseconds?: number;
301302

302303
globalPlugins?: ReadonlyArray<string>;
@@ -359,7 +360,8 @@ namespace ts.server {
359360
suppressDiagnosticEvents: this.suppressDiagnosticEvents,
360361
globalPlugins: opts.globalPlugins,
361362
pluginProbeLocations: opts.pluginProbeLocations,
362-
allowLocalPluginLoads: opts.allowLocalPluginLoads
363+
allowLocalPluginLoads: opts.allowLocalPluginLoads,
364+
syntaxOnly: opts.syntaxOnly,
363365
};
364366
this.projectService = new ProjectService(settings);
365367
this.gcTimer = new GcTimer(this.host, /*delay*/ 7000, this.logger);

src/services/services.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,8 +1152,10 @@ namespace ts {
11521152
};
11531153
}
11541154

1155-
export function createLanguageService(host: LanguageServiceHost,
1156-
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService {
1155+
export function createLanguageService(
1156+
host: LanguageServiceHost,
1157+
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()),
1158+
syntaxOnly = false): LanguageService {
11571159

11581160
const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
11591161
let program: Program;
@@ -1188,6 +1190,8 @@ namespace ts {
11881190
}
11891191

11901192
function synchronizeHostData(): void {
1193+
Debug.assert(!syntaxOnly);
1194+
11911195
// perform fast check if host supports it
11921196
if (host.getProjectVersion) {
11931197
const hostProjectVersion = host.getProjectVersion();
@@ -1363,6 +1367,11 @@ namespace ts {
13631367
}
13641368

13651369
function getProgram(): Program {
1370+
if (syntaxOnly) {
1371+
Debug.assert(program === undefined);
1372+
return undefined;
1373+
}
1374+
13661375
synchronizeHostData();
13671376

13681377
return program;

src/services/shims.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,7 @@ namespace ts {
11971197
this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory());
11981198
}
11991199
const hostAdapter = new LanguageServiceShimHostAdapter(host);
1200-
const languageService = createLanguageService(hostAdapter, this.documentRegistry);
1200+
const languageService = createLanguageService(hostAdapter, this.documentRegistry, /*syntaxOnly*/ false);
12011201
return new LanguageServiceShimObject(this, host, languageService);
12021202
}
12031203
catch (err) {

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4867,7 +4867,7 @@ declare namespace ts {
48674867
function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile;
48684868
let disableIncrementalParsing: boolean;
48694869
function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile;
4870-
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService;
4870+
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService;
48714871
/**
48724872
* Get the path of the default library files (lib.d.ts) as distributed with the typescript
48734873
* node package.
@@ -7295,6 +7295,7 @@ declare namespace ts.server {
72957295
eventHandler?: ProjectServiceEventHandler;
72967296
/** Has no effect if eventHandler is also specified. */
72977297
suppressDiagnosticEvents?: boolean;
7298+
syntaxOnly?: boolean;
72987299
throttleWaitMilliseconds?: number;
72997300
globalPlugins?: ReadonlyArray<string>;
73007301
pluginProbeLocations?: ReadonlyArray<string>;
@@ -7864,6 +7865,7 @@ declare namespace ts.server {
78647865
pluginProbeLocations?: ReadonlyArray<string>;
78657866
allowLocalPluginLoads?: boolean;
78667867
typesMapLocation?: string;
7868+
syntaxOnly?: boolean;
78677869
}
78687870
class ProjectService {
78697871
readonly typingsCache: TypingsCache;
@@ -7930,6 +7932,7 @@ declare namespace ts.server {
79307932
readonly pluginProbeLocations: ReadonlyArray<string>;
79317933
readonly allowLocalPluginLoads: boolean;
79327934
readonly typesMapLocation: string | undefined;
7935+
readonly syntaxOnly?: boolean;
79337936
/** Tracks projects that we have already sent telemetry for. */
79347937
private readonly seenProjects;
79357938
constructor(opts: ProjectServiceOptions);

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5120,7 +5120,7 @@ declare namespace ts {
51205120
function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile;
51215121
let disableIncrementalParsing: boolean;
51225122
function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile;
5123-
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService;
5123+
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService;
51245124
/**
51255125
* Get the path of the default library files (lib.d.ts) as distributed with the typescript
51265126
* node package.

0 commit comments

Comments
 (0)