Skip to content

Commit b90ce31

Browse files
committed
tsconfig.json mixed content support
1 parent c87bce1 commit b90ce31

File tree

12 files changed

+85
-30
lines changed

12 files changed

+85
-30
lines changed

src/compiler/commandLineParser.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ namespace ts {
826826
* @param basePath A root directory to resolve relative path entries in the config
827827
* file to. e.g. outDir
828828
*/
829-
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string, resolutionStack: Path[] = []): ParsedCommandLine {
829+
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string, resolutionStack: Path[] = [], mixedContentFileExtensions: string[] = []): ParsedCommandLine {
830830
const errors: Diagnostic[] = [];
831831
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
832832
const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName);
@@ -963,7 +963,7 @@ namespace ts {
963963
includeSpecs = ["**/*"];
964964
}
965965

966-
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors);
966+
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, mixedContentFileExtensions);
967967

968968
if (result.fileNames.length === 0 && !hasProperty(json, "files") && resolutionStack.length === 0) {
969969
errors.push(
@@ -1165,7 +1165,7 @@ namespace ts {
11651165
* @param host The host used to resolve files and directories.
11661166
* @param errors An array for diagnostic reporting.
11671167
*/
1168-
function matchFileNames(fileNames: string[], include: string[], exclude: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[]): ExpandResult {
1168+
function matchFileNames(fileNames: string[], include: string[], exclude: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[], mixedContentFileExtensions: string[]): ExpandResult {
11691169
basePath = normalizePath(basePath);
11701170

11711171
// The exclude spec list is converted into a regular expression, which allows us to quickly
@@ -1199,7 +1199,7 @@ namespace ts {
11991199

12001200
// Rather than requery this for each file and filespec, we query the supported extensions
12011201
// once and store it on the expansion context.
1202-
const supportedExtensions = getSupportedExtensions(options);
1202+
const supportedExtensions = getSupportedExtensions(options, mixedContentFileExtensions);
12031203

12041204
// Literal files are always included verbatim. An "include" or "exclude" specification cannot
12051205
// remove a literal file.

src/compiler/core.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,8 +1912,8 @@ namespace ts {
19121912
export const supportedJavascriptExtensions = [".js", ".jsx"];
19131913
const allSupportedExtensions = supportedTypeScriptExtensions.concat(supportedJavascriptExtensions);
19141914

1915-
export function getSupportedExtensions(options?: CompilerOptions): string[] {
1916-
return options && options.allowJs ? allSupportedExtensions : supportedTypeScriptExtensions;
1915+
export function getSupportedExtensions(options?: CompilerOptions, mixedContentFileExtensions?: string[]): string[] {
1916+
return options && options.allowJs ? concatenate(allSupportedExtensions, mixedContentFileExtensions) : supportedTypeScriptExtensions;
19171917
}
19181918

19191919
export function hasJavaScriptFileExtension(fileName: string) {
@@ -1924,10 +1924,10 @@ namespace ts {
19241924
return forEach(supportedTypeScriptExtensions, extension => fileExtensionIs(fileName, extension));
19251925
}
19261926

1927-
export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions) {
1927+
export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions, mixedContentFileExtensions?: string[]) {
19281928
if (!fileName) { return false; }
19291929

1930-
for (const extension of getSupportedExtensions(compilerOptions)) {
1930+
for (const extension of getSupportedExtensions(compilerOptions, mixedContentFileExtensions)) {
19311931
if (fileExtensionIs(fileName, extension)) {
19321932
return true;
19331933
}

src/compiler/program.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ namespace ts {
289289
return resolutions;
290290
}
291291

292-
export function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program {
292+
export function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, mixedContentFileExtensions?: string[]): Program {
293293
let program: Program;
294294
let files: SourceFile[] = [];
295295
let commonSourceDirectory: string;
@@ -324,7 +324,7 @@ namespace ts {
324324
let skipDefaultLib = options.noLib;
325325
const programDiagnostics = createDiagnosticCollection();
326326
const currentDirectory = host.getCurrentDirectory();
327-
const supportedExtensions = getSupportedExtensions(options);
327+
const supportedExtensions = getSupportedExtensions(options, mixedContentFileExtensions);
328328

329329
// Map storing if there is emit blocking diagnostics for given input
330330
const hasEmitBlockingDiagnostics = createFileMap<boolean>(getCanonicalFileName);

src/server/editorServices.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ namespace ts.server {
9090
export interface HostConfiguration {
9191
formatCodeOptions: FormatCodeSettings;
9292
hostInfo: string;
93+
mixedContentFileExtensions?: string[];
9394
}
9495

9596
interface ConfigFileConversionResult {
@@ -114,13 +115,13 @@ namespace ts.server {
114115
interface FilePropertyReader<T> {
115116
getFileName(f: T): string;
116117
getScriptKind(f: T): ScriptKind;
117-
hasMixedContent(f: T): boolean;
118+
hasMixedContent(f: T, mixedContentFileExtensions: string[]): boolean;
118119
}
119120

120121
const fileNamePropertyReader: FilePropertyReader<string> = {
121122
getFileName: x => x,
122123
getScriptKind: _ => undefined,
123-
hasMixedContent: _ => false
124+
hasMixedContent: (fileName, mixedContentFileExtensions) => forEach(mixedContentFileExtensions, extension => fileExtensionIs(fileName, extension))
124125
};
125126

126127
const externalFilePropertyReader: FilePropertyReader<protocol.ExternalFile> = {
@@ -235,12 +236,12 @@ namespace ts.server {
235236
private readonly directoryWatchers: DirectoryWatchers;
236237
private readonly throttledOperations: ThrottledOperations;
237238

238-
private readonly hostConfiguration: HostConfiguration;
239-
240239
private changedFiles: ScriptInfo[];
241240

242241
private toCanonicalFileName: (f: string) => string;
243242

243+
public readonly hostConfiguration: HostConfiguration;
244+
244245
public lastDeletedFile: ScriptInfo;
245246

246247
constructor(public readonly host: ServerHost,
@@ -264,7 +265,8 @@ namespace ts.server {
264265

265266
this.hostConfiguration = {
266267
formatCodeOptions: getDefaultFormatCodeSettings(this.host),
267-
hostInfo: "Unknown host"
268+
hostInfo: "Unknown host",
269+
mixedContentFileExtensions: []
268270
};
269271

270272
this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames, host.getCurrentDirectory());
@@ -455,7 +457,7 @@ namespace ts.server {
455457
// If a change was made inside "folder/file", node will trigger the callback twice:
456458
// one with the fileName being "folder/file", and the other one with "folder".
457459
// We don't respond to the second one.
458-
if (fileName && !ts.isSupportedSourceFileName(fileName, project.getCompilerOptions())) {
460+
if (fileName && !ts.isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.mixedContentFileExtensions)) {
459461
return;
460462
}
461463

@@ -610,6 +612,9 @@ namespace ts.server {
610612
let projectsToRemove: Project[];
611613
for (const p of info.containingProjects) {
612614
if (p.projectKind === ProjectKind.Configured) {
615+
if (info.hasMixedContent) {
616+
info.hasChanges = true;
617+
}
613618
// last open file in configured project - close it
614619
if ((<ConfiguredProject>p).deleteOpenRef() === 0) {
615620
(projectsToRemove || (projectsToRemove = [])).push(p);
@@ -772,7 +777,9 @@ namespace ts.server {
772777
this.host,
773778
getDirectoryPath(configFilename),
774779
/*existingOptions*/ {},
775-
configFilename);
780+
configFilename,
781+
/*resolutionStack*/ [],
782+
this.hostConfiguration.mixedContentFileExtensions);
776783

777784
if (parsedCommandLine.errors.length) {
778785
errors = concatenate(errors, parsedCommandLine.errors);
@@ -876,7 +883,7 @@ namespace ts.server {
876883
for (const f of files) {
877884
const rootFilename = propertyReader.getFileName(f);
878885
const scriptKind = propertyReader.getScriptKind(f);
879-
const hasMixedContent = propertyReader.hasMixedContent(f);
886+
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.mixedContentFileExtensions);
880887
if (this.host.fileExists(rootFilename)) {
881888
const info = this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(rootFilename), /*openedByClient*/ clientFileName == rootFilename, /*fileContent*/ undefined, scriptKind, hasMixedContent);
882889
project.addRoot(info);
@@ -922,7 +929,7 @@ namespace ts.server {
922929
rootFilesChanged = true;
923930
if (!scriptInfo) {
924931
const scriptKind = propertyReader.getScriptKind(f);
925-
const hasMixedContent = propertyReader.hasMixedContent(f);
932+
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.mixedContentFileExtensions);
926933
scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent);
927934
}
928935
}
@@ -1072,6 +1079,9 @@ namespace ts.server {
10721079
}
10731080
if (openedByClient) {
10741081
info.isOpen = true;
1082+
if (hasMixedContent) {
1083+
info.hasChanges = true;
1084+
}
10751085
}
10761086
}
10771087
return info;
@@ -1103,6 +1113,10 @@ namespace ts.server {
11031113
mergeMaps(this.hostConfiguration.formatCodeOptions, convertFormatOptions(args.formatOptions));
11041114
this.logger.info("Format host information updated");
11051115
}
1116+
if (args.mixedContentFileExtensions) {
1117+
this.hostConfiguration.mixedContentFileExtensions = args.mixedContentFileExtensions;
1118+
this.logger.info("Host mixed content file extensions updated");
1119+
}
11061120
}
11071121
}
11081122

@@ -1168,12 +1182,12 @@ namespace ts.server {
11681182
}
11691183

11701184
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean): OpenConfiguredProjectResult {
1185+
const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent);
11711186
const { configFileName = undefined, configFileErrors = undefined }: OpenConfiguredProjectResult = this.findContainingExternalProject(fileName)
11721187
? {}
11731188
: this.openOrUpdateConfiguredProjectForFile(fileName);
11741189

11751190
// at this point if file is the part of some configured/external project then this project should be created
1176-
const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent);
11771191
this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ true);
11781192
this.printProjects();
11791193
return { configFileName, configFileErrors };

src/server/lsHost.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace ts.server {
66
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost, ServerLanguageServiceHost {
77
private compilationSettings: ts.CompilerOptions;
8+
private mixedContentFileExtensions: string[];
89
private readonly resolvedModuleNames= createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
910
private readonly resolvedTypeReferenceDirectives = createFileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
1011
private readonly getCanonicalFileName: (fileName: string) => string;
@@ -143,6 +144,10 @@ namespace ts.server {
143144
return this.compilationSettings;
144145
}
145146

147+
getMixedContentFileExtensions() {
148+
return this.mixedContentFileExtensions;
149+
}
150+
146151
useCaseSensitiveFileNames() {
147152
return this.host.useCaseSensitiveFileNames;
148153
}
@@ -231,5 +236,9 @@ namespace ts.server {
231236
}
232237
this.compilationSettings = opt;
233238
}
239+
240+
setMixedContentFileExtensions(mixedContentFileExtensions: string[]) {
241+
this.mixedContentFileExtensions = mixedContentFileExtensions || [];
242+
}
234243
}
235244
}

src/server/project.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/// <reference path="..\services\services.ts" />
1+
/// <reference path="..\services\services.ts" />
22
/// <reference path="utilities.ts"/>
33
/// <reference path="scriptInfo.ts"/>
44
/// <reference path="lsHost.ts"/>
@@ -202,6 +202,7 @@ namespace ts.server {
202202
enableLanguageService() {
203203
const lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken);
204204
lsHost.setCompilationSettings(this.compilerOptions);
205+
lsHost.setMixedContentFileExtensions(this.projectService.hostConfiguration.mixedContentFileExtensions);
205206
this.languageService = ts.createLanguageService(lsHost, this.documentRegistry);
206207

207208
this.lsHost = lsHost;
@@ -462,6 +463,10 @@ namespace ts.server {
462463
return !hasChanges;
463464
}
464465

466+
private hasChangedFiles() {
467+
return this.rootFiles && forEach(this.rootFiles, info => info.hasChanges);
468+
}
469+
465470
private setTypings(typings: SortedReadonlyArray<string>): boolean {
466471
if (arrayIsEqualTo(this.typingFiles, typings)) {
467472
return false;
@@ -475,7 +480,7 @@ namespace ts.server {
475480
const oldProgram = this.program;
476481
this.program = this.languageService.getProgram();
477482

478-
let hasChanges = false;
483+
let hasChanges = this.hasChangedFiles();
479484
// bump up the version if
480485
// - oldProgram is not set - this is a first time updateGraph is called
481486
// - newProgram is different from the old program and structure of the old program was not reused.
@@ -578,6 +583,7 @@ namespace ts.server {
578583

579584
const added: string[] = [];
580585
const removed: string[] = [];
586+
const updated = this.rootFiles.filter(info => info.hasChanges).map(info => info.fileName);
581587
for (const id in currentFiles) {
582588
if (!hasProperty(lastReportedFileNames, id)) {
583589
added.push(id);
@@ -588,9 +594,12 @@ namespace ts.server {
588594
removed.push(id);
589595
}
590596
}
597+
for (const root of this.rootFiles) {
598+
root.hasChanges = false;
599+
}
591600
this.lastReportedFileNames = currentFiles;
592601
this.lastReportedVersion = this.projectStructureVersion;
593-
return { info, changes: { added, removed }, projectErrors: this.projectErrors };
602+
return { info, changes: { added, removed, updated }, projectErrors: this.projectErrors };
594603
}
595604
else {
596605
// unknown version - return everything

src/server/protocol.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,10 @@ namespace ts.server.protocol {
914914
* List of removed files
915915
*/
916916
removed: string[];
917+
/**
918+
* List of updated files
919+
*/
920+
updated: string[];
917921
}
918922

919923
/**
@@ -986,6 +990,11 @@ namespace ts.server.protocol {
986990
* The format options to use during formatting and other code editing features.
987991
*/
988992
formatOptions?: FormatCodeSettings;
993+
994+
/**
995+
* List of host's supported mixed content file extensions
996+
*/
997+
mixedContentFileExtensions?: string[];
989998
}
990999

9911000
/**

src/server/scriptInfo.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ namespace ts.server {
1313
private fileWatcher: FileWatcher;
1414
private svc: ScriptVersionCache;
1515

16-
// TODO: allow to update hasMixedContent from the outside
1716
constructor(
1817
private readonly host: ServerHost,
1918
readonly fileName: NormalizedPath,
@@ -29,6 +28,8 @@ namespace ts.server {
2928
: getScriptKindFromFileName(fileName);
3029
}
3130

31+
public hasChanges = false;
32+
3233
getFormatCodeSettings() {
3334
return this.formatCodeSettings;
3435
}

src/server/utilities.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/// <reference path="types.d.ts" />
1+
/// <reference path="types.d.ts" />
22
/// <reference path="shared.ts" />
33

44
namespace ts.server {
@@ -211,13 +211,15 @@ namespace ts.server {
211211

212212
export interface ServerLanguageServiceHost {
213213
setCompilationSettings(options: CompilerOptions): void;
214+
setMixedContentFileExtensions(mixedContentFileExtensions: string[]): void;
214215
notifyFileRemoved(info: ScriptInfo): void;
215216
startRecordingFilesWithChangedResolutions(): void;
216217
finishRecordingFilesWithChangedResolutions(): Path[];
217218
}
218219

219220
export const nullLanguageServiceHost: ServerLanguageServiceHost = {
220221
setCompilationSettings: () => undefined,
222+
setMixedContentFileExtensions: () => undefined,
221223
notifyFileRemoved: () => undefined,
222224
startRecordingFilesWithChangedResolutions: () => undefined,
223225
finishRecordingFilesWithChangedResolutions: () => undefined

0 commit comments

Comments
 (0)