Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7dd30db

Browse filesBrowse files
committed
tsconfig.json mixed content support
1 parent c87bce1 commit 7dd30db
Copy full SHA for 7dd30db

14 files changed

+159-30Lines changed: 159 additions & 30 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/commandLineParser.ts‎

Copy file name to clipboardExpand all lines: src/compiler/commandLineParser.ts
+4-4Lines 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[] = [], fileExtensionMap: FileExtensionMap = {}): 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, fileExtensionMap);
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[], fileExtensionMap: FileExtensionMap): 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, fileExtensionMap);
12031203

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

‎src/compiler/core.ts‎

Copy file name to clipboardExpand all lines: src/compiler/core.ts
+12-4Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,8 +1912,16 @@ 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, fileExtensionMap?: FileExtensionMap): string[] {
1916+
let typeScriptHostExtensions: string[] = [];
1917+
let allHostExtensions: string[] = [];
1918+
if (fileExtensionMap) {
1919+
allHostExtensions = concatenate(concatenate(fileExtensionMap.javaScript, fileExtensionMap.typeScript), fileExtensionMap.mixedContent);
1920+
typeScriptHostExtensions = fileExtensionMap.typeScript;
1921+
}
1922+
const allTypeScriptExtensions = concatenate(supportedTypeScriptExtensions, typeScriptHostExtensions);
1923+
const allExtensions = concatenate(allSupportedExtensions, allHostExtensions);
1924+
return options && options.allowJs ? allExtensions : allTypeScriptExtensions;
19171925
}
19181926

19191927
export function hasJavaScriptFileExtension(fileName: string) {
@@ -1924,10 +1932,10 @@ namespace ts {
19241932
return forEach(supportedTypeScriptExtensions, extension => fileExtensionIs(fileName, extension));
19251933
}
19261934

1927-
export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions) {
1935+
export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions, fileExtensionMap?: FileExtensionMap) {
19281936
if (!fileName) { return false; }
19291937

1930-
for (const extension of getSupportedExtensions(compilerOptions)) {
1938+
for (const extension of getSupportedExtensions(compilerOptions, fileExtensionMap)) {
19311939
if (fileExtensionIs(fileName, extension)) {
19321940
return true;
19331941
}
Collapse file

‎src/compiler/program.ts‎

Copy file name to clipboardExpand all lines: src/compiler/program.ts
+2-2Lines 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, fileExtensionMap?: FileExtensionMap): 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, fileExtensionMap);
328328

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

‎src/compiler/types.ts‎

Copy file name to clipboardExpand all lines: src/compiler/types.ts
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2997,6 +2997,12 @@ namespace ts {
29972997
ThisProperty
29982998
}
29992999

3000+
export interface FileExtensionMap {
3001+
javaScript?: string[];
3002+
typeScript?: string[];
3003+
mixedContent?: string[];
3004+
}
3005+
30003006
export interface DiagnosticMessage {
30013007
key: string;
30023008
category: DiagnosticCategory;
Collapse file

‎src/harness/unittests/tsserverProjectSystem.ts‎

Copy file name to clipboardExpand all lines: src/harness/unittests/tsserverProjectSystem.ts
+60Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,6 +1441,66 @@ namespace ts.projectSystem {
14411441
checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]);
14421442
});
14431443

1444+
it("tsconfig script block support", () => {
1445+
const file1 = {
1446+
path: "/a/b/f1.ts",
1447+
content: ` `
1448+
};
1449+
const file2 = {
1450+
path: "/a/b/f2.html",
1451+
content: `var hello = "hello";`
1452+
};
1453+
const config = {
1454+
path: "/a/b/tsconfig.json",
1455+
content: JSON.stringify({ compilerOptions: { allowJs: true } })
1456+
};
1457+
const host = createServerHost([file1, file2, config]);
1458+
const session = createSession(host);
1459+
openFilesForSession([file1], session);
1460+
const projectService = session.getProjectService();
1461+
1462+
// HTML file will not be included in any projects yet
1463+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
1464+
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path]);
1465+
1466+
// Specify .html extension as mixed content
1467+
const configureHostRequest = makeSessionRequest<protocol.ConfigureRequestArguments>(CommandNames.Configure, { fileExtensionMap: { mixedContent: [".html"] } });
1468+
session.executeCommand(configureHostRequest).response;
1469+
1470+
// HTML file still not included in the project as it is closed
1471+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
1472+
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path]);
1473+
1474+
// Open HTML file
1475+
projectService.applyChangesInOpenFiles(
1476+
/*openFiles*/[{ fileName: file2.path, hasMixedContent: true, scriptKind: ScriptKind.JS, content: `var hello = "hello";` }],
1477+
/*changedFiles*/undefined,
1478+
/*closedFiles*/undefined);
1479+
1480+
// Now HTML file is included in the project
1481+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
1482+
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path]);
1483+
1484+
// Check identifiers defined in HTML content are available in .ts file
1485+
const project = projectService.configuredProjects[0];
1486+
let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1);
1487+
assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`);
1488+
1489+
// Close HTML file
1490+
projectService.applyChangesInOpenFiles(
1491+
/*openFiles*/undefined,
1492+
/*changedFiles*/undefined,
1493+
/*closedFiles*/[file2.path]);
1494+
1495+
// HTML file is still included in project
1496+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
1497+
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path]);
1498+
1499+
// Check identifiers defined in HTML content are not available in .ts file
1500+
completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5);
1501+
assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`);
1502+
});
1503+
14441504
it("project structure update is deferred if files are not added\removed", () => {
14451505
const file1 = {
14461506
path: "/a/b/f1.ts",
Collapse file

‎src/server/editorServices.ts‎

Copy file name to clipboardExpand all lines: src/server/editorServices.ts
+24-10Lines 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+
fileExtensionMap?: FileExtensionMap;
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, mixedContentExtensions: string[]): boolean;
118119
}
119120

120121
const fileNamePropertyReader: FilePropertyReader<string> = {
121122
getFileName: x => x,
122123
getScriptKind: _ => undefined,
123-
hasMixedContent: _ => false
124+
hasMixedContent: (fileName, mixedContentExtensions) => forEach(mixedContentExtensions, 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+
fileExtensionMap: {}
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.fileExtensionMap)) {
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.fileExtensionMap);
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.fileExtensionMap.mixedContent);
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.fileExtensionMap.mixedContent);
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.fileExtensionMap) {
1117+
this.hostConfiguration.fileExtensionMap = args.fileExtensionMap;
1118+
this.logger.info("Host file extension mappings 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 };
Collapse file

‎src/server/lsHost.ts‎

Copy file name to clipboardExpand all lines: src/server/lsHost.ts
+9Lines 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 fileExtensionMap: FileExtensionMap;
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+
getFileExtensionMap() {
148+
return this.fileExtensionMap;
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+
setFileExtensionMap(fileExtensionMap: FileExtensionMap) {
241+
this.fileExtensionMap = fileExtensionMap || {};
242+
}
234243
}
235244
}
Collapse file

‎src/server/project.ts‎

Copy file name to clipboardExpand all lines: src/server/project.ts
+12-3Lines 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.setFileExtensionMap(this.projectService.hostConfiguration.fileExtensionMap);
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

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.