Skip to content

Commit 1ba6c86

Browse files
author
Arthur Ozga
committed
Merge branch 'master' into interfaceFixes
2 parents 469745b + 7c5c664 commit 1ba6c86

26 files changed

+2057
-787
lines changed

src/compiler/checker.ts

Lines changed: 104 additions & 50 deletions
Large diffs are not rendered by default.

src/compiler/commandLineParser.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ namespace ts {
841841
* @param basePath A root directory to resolve relative path entries in the config
842842
* file to. e.g. outDir
843843
*/
844-
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string, resolutionStack: Path[] = []): ParsedCommandLine {
844+
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string, resolutionStack: Path[] = [], extraFileExtensions: FileExtensionInfo[] = []): ParsedCommandLine {
845845
const errors: Diagnostic[] = [];
846846
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
847847
const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName);
@@ -981,7 +981,7 @@ namespace ts {
981981
includeSpecs = ["**/*"];
982982
}
983983

984-
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors);
984+
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, extraFileExtensions);
985985

986986
if (result.fileNames.length === 0 && !hasProperty(json, "files") && resolutionStack.length === 0) {
987987
errors.push(
@@ -1185,7 +1185,7 @@ namespace ts {
11851185
* @param host The host used to resolve files and directories.
11861186
* @param errors An array for diagnostic reporting.
11871187
*/
1188-
function matchFileNames(fileNames: string[], include: string[], exclude: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[]): ExpandResult {
1188+
function matchFileNames(fileNames: string[], include: string[], exclude: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[], extraFileExtensions: FileExtensionInfo[]): ExpandResult {
11891189
basePath = normalizePath(basePath);
11901190

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

12201220
// Rather than requery this for each file and filespec, we query the supported extensions
12211221
// once and store it on the expansion context.
1222-
const supportedExtensions = getSupportedExtensions(options);
1222+
const supportedExtensions = getSupportedExtensions(options, extraFileExtensions);
12231223

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

src/compiler/core.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,8 +1924,18 @@ namespace ts {
19241924
export const supportedJavascriptExtensions = [".js", ".jsx"];
19251925
const allSupportedExtensions = supportedTypeScriptExtensions.concat(supportedJavascriptExtensions);
19261926

1927-
export function getSupportedExtensions(options?: CompilerOptions): string[] {
1928-
return options && options.allowJs ? allSupportedExtensions : supportedTypeScriptExtensions;
1927+
export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: FileExtensionInfo[]): string[] {
1928+
const needAllExtensions = options && options.allowJs;
1929+
if (!extraFileExtensions || extraFileExtensions.length === 0) {
1930+
return needAllExtensions ? allSupportedExtensions : supportedTypeScriptExtensions;
1931+
}
1932+
const extensions = (needAllExtensions ? allSupportedExtensions : supportedTypeScriptExtensions).slice(0);
1933+
for (const extInfo of extraFileExtensions) {
1934+
if (needAllExtensions || extInfo.scriptKind === ScriptKind.TS) {
1935+
extensions.push(extInfo.extension);
1936+
}
1937+
}
1938+
return extensions;
19291939
}
19301940

19311941
export function hasJavaScriptFileExtension(fileName: string) {
@@ -1936,10 +1946,10 @@ namespace ts {
19361946
return forEach(supportedTypeScriptExtensions, extension => fileExtensionIs(fileName, extension));
19371947
}
19381948

1939-
export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions) {
1949+
export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions, extraFileExtensions?: FileExtensionInfo[]) {
19401950
if (!fileName) { return false; }
19411951

1942-
for (const extension of getSupportedExtensions(compilerOptions)) {
1952+
for (const extension of getSupportedExtensions(compilerOptions, extraFileExtensions)) {
19431953
if (fileExtensionIs(fileName, extension)) {
19441954
return true;
19451955
}

src/compiler/types.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2944,6 +2944,7 @@ namespace ts {
29442944
typeParameter?: TypeParameter;
29452945
constraintType?: Type;
29462946
templateType?: Type;
2947+
modifiersType?: Type;
29472948
mapper?: TypeMapper; // Instantiation mapper
29482949
}
29492950

@@ -2979,6 +2980,8 @@ namespace ts {
29792980
}
29802981

29812982
export interface TypeVariable extends Type {
2983+
/* @internal */
2984+
resolvedApparentType: Type;
29822985
/* @internal */
29832986
resolvedIndexType: IndexType;
29842987
}
@@ -2991,8 +2994,6 @@ namespace ts {
29912994
/* @internal */
29922995
mapper?: TypeMapper; // Instantiation mapper
29932996
/* @internal */
2994-
resolvedApparentType: Type;
2995-
/* @internal */
29962997
isThisType?: boolean;
29972998
}
29982999

@@ -3001,6 +3002,7 @@ namespace ts {
30013002
export interface IndexedAccessType extends TypeVariable {
30023003
objectType: Type;
30033004
indexType: Type;
3005+
constraint?: Type;
30043006
}
30053007

30063008
// keyof T types (TypeFlags.Index)
@@ -3097,6 +3099,12 @@ namespace ts {
30973099
ThisProperty
30983100
}
30993101

3102+
export interface FileExtensionInfo {
3103+
extension: string;
3104+
scriptKind: ScriptKind;
3105+
isMixedContent: boolean;
3106+
}
3107+
31003108
export interface DiagnosticMessage {
31013109
key: string;
31023110
category: DiagnosticCategory;

src/harness/unittests/textStorage.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ namespace ts.textStorage {
1616

1717
it("text based storage should be have exactly the same as script version cache", () => {
1818

19-
debugger
2019
const host = ts.projectSystem.createServerHost([f]);
2120

2221
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path));

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ namespace ts.projectSystem {
140140
export interface TestServerHostCreationParameters {
141141
useCaseSensitiveFileNames?: boolean;
142142
executingFilePath?: string;
143-
libFile?: FileOrFolder;
144143
currentDirectory?: string;
145144
}
146145

@@ -1145,6 +1144,69 @@ namespace ts.projectSystem {
11451144
checkNumberOfProjects(projectService, {});
11461145
});
11471146

1147+
it("reload regular file after closing", () => {
1148+
const f1 = {
1149+
path: "/a/b/app.ts",
1150+
content: "x."
1151+
};
1152+
const f2 = {
1153+
path: "/a/b/lib.ts",
1154+
content: "let x: number;"
1155+
};
1156+
1157+
const host = createServerHost([f1, f2, libFile]);
1158+
const service = createProjectService(host);
1159+
service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: toExternalFiles([f1.path, f2.path]), options: {} })
1160+
1161+
service.openClientFile(f1.path);
1162+
service.openClientFile(f2.path, "let x: string");
1163+
1164+
service.checkNumberOfProjects({ externalProjects: 1 });
1165+
checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]);
1166+
1167+
const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2);
1168+
// should contain completions for string
1169+
assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'");
1170+
assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'");
1171+
1172+
service.closeClientFile(f2.path);
1173+
const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2);
1174+
// should contain completions for string
1175+
assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'");
1176+
assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'");
1177+
});
1178+
1179+
it("clear mixed content file after closing", () => {
1180+
const f1 = {
1181+
path: "/a/b/app.ts",
1182+
content: " "
1183+
};
1184+
const f2 = {
1185+
path: "/a/b/lib.html",
1186+
content: "<html/>"
1187+
};
1188+
1189+
const host = createServerHost([f1, f2, libFile]);
1190+
const service = createProjectService(host);
1191+
service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} })
1192+
1193+
service.openClientFile(f1.path);
1194+
service.openClientFile(f2.path, "let somelongname: string");
1195+
1196+
service.checkNumberOfProjects({ externalProjects: 1 });
1197+
checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]);
1198+
1199+
const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0);
1200+
assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'");
1201+
1202+
service.closeClientFile(f2.path);
1203+
const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0);
1204+
assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'");
1205+
const sf2 = service.externalProjects[0].getLanguageService().getProgram().getSourceFile(f2.path);
1206+
assert.equal(sf2.text, "");
1207+
});
1208+
1209+
11481210
it("external project with included config file opened after configured project", () => {
11491211
const file1 = {
11501212
path: "/a/b/f1.ts",
@@ -1529,6 +1591,67 @@ namespace ts.projectSystem {
15291591
checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]);
15301592
});
15311593

1594+
it("tsconfig script block support", () => {
1595+
const file1 = {
1596+
path: "/a/b/f1.ts",
1597+
content: ` `
1598+
};
1599+
const file2 = {
1600+
path: "/a/b/f2.html",
1601+
content: `var hello = "hello";`
1602+
};
1603+
const config = {
1604+
path: "/a/b/tsconfig.json",
1605+
content: JSON.stringify({ compilerOptions: { allowJs: true } })
1606+
};
1607+
const host = createServerHost([file1, file2, config]);
1608+
const session = createSession(host);
1609+
openFilesForSession([file1], session);
1610+
const projectService = session.getProjectService();
1611+
1612+
// HTML file will not be included in any projects yet
1613+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
1614+
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path]);
1615+
1616+
// Specify .html extension as mixed content
1617+
const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }];
1618+
const configureHostRequest = makeSessionRequest<protocol.ConfigureRequestArguments>(CommandNames.Configure, { extraFileExtensions });
1619+
session.executeCommand(configureHostRequest).response;
1620+
1621+
// HTML file still not included in the project as it is closed
1622+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
1623+
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path]);
1624+
1625+
// Open HTML file
1626+
projectService.applyChangesInOpenFiles(
1627+
/*openFiles*/[{ fileName: file2.path, hasMixedContent: true, scriptKind: ScriptKind.JS, content: `var hello = "hello";` }],
1628+
/*changedFiles*/undefined,
1629+
/*closedFiles*/undefined);
1630+
1631+
// Now HTML file is included in the project
1632+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
1633+
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path]);
1634+
1635+
// Check identifiers defined in HTML content are available in .ts file
1636+
const project = projectService.configuredProjects[0];
1637+
let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1);
1638+
assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`);
1639+
1640+
// Close HTML file
1641+
projectService.applyChangesInOpenFiles(
1642+
/*openFiles*/undefined,
1643+
/*changedFiles*/undefined,
1644+
/*closedFiles*/[file2.path]);
1645+
1646+
// HTML file is still included in project
1647+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
1648+
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path]);
1649+
1650+
// Check identifiers defined in HTML content are not available in .ts file
1651+
completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5);
1652+
assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`);
1653+
});
1654+
15321655
it("project structure update is deferred if files are not added\removed", () => {
15331656
const file1 = {
15341657
path: "/a/b/f1.ts",

src/server/editorServices.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ namespace ts.server {
108108
export interface HostConfiguration {
109109
formatCodeOptions: FormatCodeSettings;
110110
hostInfo: string;
111+
extraFileExtensions?: FileExtensionInfo[];
111112
}
112113

113114
interface ConfigFileConversionResult {
@@ -132,13 +133,16 @@ namespace ts.server {
132133
interface FilePropertyReader<T> {
133134
getFileName(f: T): string;
134135
getScriptKind(f: T): ScriptKind;
135-
hasMixedContent(f: T): boolean;
136+
hasMixedContent(f: T, extraFileExtensions: FileExtensionInfo[]): boolean;
136137
}
137138

138139
const fileNamePropertyReader: FilePropertyReader<string> = {
139140
getFileName: x => x,
140141
getScriptKind: _ => undefined,
141-
hasMixedContent: _ => false
142+
hasMixedContent: (fileName, extraFileExtensions) => {
143+
const mixedContentExtensions = ts.map(ts.filter(extraFileExtensions, item => item.isMixedContent), item => item.extension);
144+
return forEach(mixedContentExtensions, extension => fileExtensionIs(fileName, extension))
145+
}
142146
};
143147

144148
const externalFilePropertyReader: FilePropertyReader<protocol.ExternalFile> = {
@@ -282,7 +286,8 @@ namespace ts.server {
282286

283287
this.hostConfiguration = {
284288
formatCodeOptions: getDefaultFormatCodeSettings(this.host),
285-
hostInfo: "Unknown host"
289+
hostInfo: "Unknown host",
290+
extraFileExtensions: []
286291
};
287292

288293
this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames, host.getCurrentDirectory());
@@ -486,7 +491,7 @@ namespace ts.server {
486491
// If a change was made inside "folder/file", node will trigger the callback twice:
487492
// one with the fileName being "folder/file", and the other one with "folder".
488493
// We don't respond to the second one.
489-
if (fileName && !ts.isSupportedSourceFileName(fileName, project.getCompilerOptions())) {
494+
if (fileName && !ts.isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) {
490495
return;
491496
}
492497

@@ -642,6 +647,9 @@ namespace ts.server {
642647
let projectsToRemove: Project[];
643648
for (const p of info.containingProjects) {
644649
if (p.projectKind === ProjectKind.Configured) {
650+
if (info.hasMixedContent) {
651+
info.registerFileUpdate();
652+
}
645653
// last open file in configured project - close it
646654
if ((<ConfiguredProject>p).deleteOpenRef() === 0) {
647655
(projectsToRemove || (projectsToRemove = [])).push(p);
@@ -810,7 +818,9 @@ namespace ts.server {
810818
this.host,
811819
getDirectoryPath(configFilename),
812820
/*existingOptions*/ {},
813-
configFilename);
821+
configFilename,
822+
/*resolutionStack*/ [],
823+
this.hostConfiguration.extraFileExtensions);
814824

815825
if (parsedCommandLine.errors.length) {
816826
errors = concatenate(errors, parsedCommandLine.errors);
@@ -914,7 +924,7 @@ namespace ts.server {
914924
for (const f of files) {
915925
const rootFilename = propertyReader.getFileName(f);
916926
const scriptKind = propertyReader.getScriptKind(f);
917-
const hasMixedContent = propertyReader.hasMixedContent(f);
927+
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions);
918928
if (this.host.fileExists(rootFilename)) {
919929
const info = this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(rootFilename), /*openedByClient*/ clientFileName == rootFilename, /*fileContent*/ undefined, scriptKind, hasMixedContent);
920930
project.addRoot(info);
@@ -960,7 +970,7 @@ namespace ts.server {
960970
rootFilesChanged = true;
961971
if (!scriptInfo) {
962972
const scriptKind = propertyReader.getScriptKind(f);
963-
const hasMixedContent = propertyReader.hasMixedContent(f);
973+
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions);
964974
scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent);
965975
}
966976
}
@@ -1110,6 +1120,9 @@ namespace ts.server {
11101120
if (info) {
11111121
if (openedByClient && !info.isScriptOpen()) {
11121122
info.open(fileContent);
1123+
if (hasMixedContent) {
1124+
info.registerFileUpdate();
1125+
}
11131126
}
11141127
else if (fileContent !== undefined) {
11151128
info.reload(fileContent);
@@ -1144,6 +1157,10 @@ namespace ts.server {
11441157
mergeMaps(this.hostConfiguration.formatCodeOptions, convertFormatOptions(args.formatOptions));
11451158
this.logger.info("Format host information updated");
11461159
}
1160+
if (args.extraFileExtensions) {
1161+
this.hostConfiguration.extraFileExtensions = args.extraFileExtensions;
1162+
this.logger.info("Host file extension mappings updated");
1163+
}
11471164
}
11481165
}
11491166

0 commit comments

Comments
 (0)