Skip to content

Commit 81fc759

Browse files
authored
Merge pull request #11550 from Microsoft/vladima/generate-protocol
Automatically generate protocol.d.ts by pulling in necessary dependencies
2 parents d6eab36 + c0d1c31 commit 81fc759

File tree

14 files changed

+530
-199
lines changed

14 files changed

+530
-199
lines changed

Jakefile.js

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ var serverCoreSources = [
134134
"lsHost.ts",
135135
"project.ts",
136136
"editorServices.ts",
137-
"protocol.d.ts",
137+
"protocol.ts",
138138
"session.ts",
139139
"server.ts"
140140
].map(function (f) {
@@ -158,14 +158,13 @@ var typingsInstallerSources = [
158158
var serverSources = serverCoreSources.concat(servicesSources);
159159

160160
var languageServiceLibrarySources = [
161-
"protocol.d.ts",
161+
"protocol.ts",
162162
"utilities.ts",
163163
"scriptVersionCache.ts",
164164
"scriptInfo.ts",
165165
"lsHost.ts",
166166
"project.ts",
167167
"editorServices.ts",
168-
"protocol.d.ts",
169168
"session.ts",
170169

171170
].map(function (f) {
@@ -218,15 +217,14 @@ var harnessSources = harnessCoreSources.concat([
218217
].map(function (f) {
219218
return path.join(unittestsDirectory, f);
220219
})).concat([
221-
"protocol.d.ts",
220+
"protocol.ts",
222221
"utilities.ts",
223222
"scriptVersionCache.ts",
224223
"scriptInfo.ts",
225224
"lsHost.ts",
226225
"project.ts",
227226
"typingsCache.ts",
228227
"editorServices.ts",
229-
"protocol.d.ts",
230228
"session.ts",
231229
].map(function (f) {
232230
return path.join(serverDirectory, f);
@@ -474,6 +472,40 @@ compileFile(processDiagnosticMessagesJs,
474472
[],
475473
/*useBuiltCompiler*/ false);
476474

475+
var buildProtocolTs = path.join(scriptsDirectory, "buildProtocol.ts");
476+
var buildProtocolJs = path.join(scriptsDirectory, "buildProtocol.js");
477+
var buildProtocolDts = path.join(builtLocalDirectory, "protocol.d.ts");
478+
var typescriptServicesDts = path.join(builtLocalDirectory, "typescriptServices.d.ts");
479+
480+
file(buildProtocolTs);
481+
482+
compileFile(buildProtocolJs,
483+
[buildProtocolTs],
484+
[buildProtocolTs],
485+
[],
486+
/*useBuiltCompiler*/ false,
487+
{noOutFile: true});
488+
489+
file(buildProtocolDts, [buildProtocolTs, buildProtocolJs, typescriptServicesDts], function() {
490+
491+
var protocolTs = path.join(serverDirectory, "protocol.ts");
492+
493+
var cmd = host + " " + buildProtocolJs + " "+ protocolTs + " " + typescriptServicesDts + " " + buildProtocolDts;
494+
console.log(cmd);
495+
var ex = jake.createExec([cmd]);
496+
// Add listeners for output and error
497+
ex.addListener("stdout", function (output) {
498+
process.stdout.write(output);
499+
});
500+
ex.addListener("stderr", function (error) {
501+
process.stderr.write(error);
502+
});
503+
ex.addListener("cmdEnd", function () {
504+
complete();
505+
});
506+
ex.run();
507+
}, { async: true })
508+
477509
// The generated diagnostics map; built for the compiler and for the 'generate-diagnostics' task
478510
file(diagnosticInfoMapTs, [processDiagnosticMessagesJs, diagnosticMessagesJson], function () {
479511
var cmd = host + " " + processDiagnosticMessagesJs + " " + diagnosticMessagesJson;
@@ -611,6 +643,8 @@ compileFile(
611643
inlineSourceMap: true
612644
});
613645

646+
file(typescriptServicesDts, [servicesFile]);
647+
614648
var cancellationTokenFile = path.join(builtLocalDirectory, "cancellationToken.js");
615649
compileFile(cancellationTokenFile, cancellationTokenSources, [builtLocalDirectory].concat(cancellationTokenSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true, { outDir: builtLocalDirectory, noOutFile: true });
616650

@@ -645,7 +679,7 @@ task("build-fold-end", [], function () {
645679

646680
// Local target to build the compiler and services
647681
desc("Builds the full compiler and services");
648-
task("local", ["build-fold-start", "generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile, serverFile, builtGeneratedDiagnosticMessagesJSON, "lssl", "build-fold-end"]);
682+
task("local", ["build-fold-start", "generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile, serverFile, buildProtocolDts, builtGeneratedDiagnosticMessagesJSON, "lssl", "build-fold-end"]);
649683

650684
// Local target to build only tsc.js
651685
desc("Builds only the compiler");
@@ -701,7 +735,7 @@ task("generate-spec", [specMd]);
701735
// Makes a new LKG. This target does not build anything, but errors if not all the outputs are present in the built/local directory
702736
desc("Makes a new LKG out of the built js files");
703737
task("LKG", ["clean", "release", "local"].concat(libraryTargets), function () {
704-
var expectedFiles = [tscFile, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile, cancellationTokenFile, typingsInstallerFile].concat(libraryTargets);
738+
var expectedFiles = [tscFile, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile, cancellationTokenFile, typingsInstallerFile, buildProtocolDts].concat(libraryTargets);
705739
var missingFiles = expectedFiles.filter(function (f) {
706740
return !fs.existsSync(f);
707741
});

scripts/buildProtocol.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/// <reference types="node"/>
2+
3+
import * as ts from "../lib/typescript";
4+
import * as path from "path";
5+
6+
function endsWith(s: string, suffix: string) {
7+
return s.lastIndexOf(suffix, s.length - suffix.length) !== -1;
8+
}
9+
10+
class DeclarationsWalker {
11+
private visitedTypes: ts.Type[] = [];
12+
private text = "";
13+
private constructor(private typeChecker: ts.TypeChecker, private protocolFile: ts.SourceFile) {
14+
}
15+
16+
static getExtraDeclarations(typeChecker: ts.TypeChecker, protocolFile: ts.SourceFile): string {
17+
let text = "declare namespace ts.server.protocol {\n";
18+
var walker = new DeclarationsWalker(typeChecker, protocolFile);
19+
walker.visitTypeNodes(protocolFile);
20+
return walker.text
21+
? `declare namespace ts.server.protocol {\n${walker.text}}`
22+
: "";
23+
}
24+
25+
private processType(type: ts.Type): void {
26+
if (this.visitedTypes.indexOf(type) >= 0) {
27+
return;
28+
}
29+
this.visitedTypes.push(type);
30+
let s = type.aliasSymbol || type.getSymbol();
31+
if (!s) {
32+
return;
33+
}
34+
if (s.name === "Array") {
35+
// we should process type argument instead
36+
return this.processType((<any>type).typeArguments[0]);
37+
}
38+
else {
39+
for (const decl of s.getDeclarations()) {
40+
const sourceFile = decl.getSourceFile();
41+
if (sourceFile === this.protocolFile || path.basename(sourceFile.fileName) === "lib.d.ts") {
42+
return;
43+
}
44+
// splice declaration in final d.ts file
45+
const text = decl.getFullText();
46+
this.text += `${text}\n`;
47+
48+
// recursively pull all dependencies into result dts file
49+
this.visitTypeNodes(decl);
50+
}
51+
}
52+
}
53+
54+
private visitTypeNodes(node: ts.Node) {
55+
if (node.parent) {
56+
switch (node.parent.kind) {
57+
case ts.SyntaxKind.VariableDeclaration:
58+
case ts.SyntaxKind.MethodDeclaration:
59+
case ts.SyntaxKind.MethodSignature:
60+
case ts.SyntaxKind.PropertyDeclaration:
61+
case ts.SyntaxKind.PropertySignature:
62+
case ts.SyntaxKind.Parameter:
63+
case ts.SyntaxKind.IndexSignature:
64+
if (((<ts.VariableDeclaration | ts.MethodDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration | ts.PropertySignature | ts.MethodSignature | ts.IndexSignatureDeclaration>node.parent).type) === node) {
65+
const type = this.typeChecker.getTypeAtLocation(node);
66+
if (type && !(type.flags & ts.TypeFlags.TypeParameter)) {
67+
this.processType(type);
68+
}
69+
}
70+
break;
71+
}
72+
}
73+
ts.forEachChild(node, n => this.visitTypeNodes(n));
74+
}
75+
}
76+
77+
function generateProtocolFile(protocolTs: string, typeScriptServicesDts: string): string {
78+
const options = { target: ts.ScriptTarget.ES5, declaration: true, noResolve: true, types: <string[]>[], stripInternal: true };
79+
80+
/**
81+
* 1st pass - generate a program from protocol.ts and typescriptservices.d.ts and emit core version of protocol.d.ts with all internal members stripped
82+
* @return text of protocol.d.t.s
83+
*/
84+
function getInitialDtsFileForProtocol() {
85+
const program = ts.createProgram([protocolTs, typeScriptServicesDts], options);
86+
87+
let protocolDts: string;
88+
program.emit(program.getSourceFile(protocolTs), (file, content) => {
89+
if (endsWith(file, ".d.ts")) {
90+
protocolDts = content;
91+
}
92+
});
93+
if (protocolDts === undefined) {
94+
throw new Error(`Declaration file for protocol.ts is not generated`)
95+
}
96+
return protocolDts;
97+
}
98+
99+
const protocolFileName = "protocol.d.ts";
100+
/**
101+
* Second pass - generate a program from protocol.d.ts and typescriptservices.d.ts, then augment core protocol.d.ts with extra types from typescriptservices.d.ts
102+
*/
103+
function getProgramWithProtocolText(protocolDts: string, includeTypeScriptServices: boolean) {
104+
const host = ts.createCompilerHost(options);
105+
const originalGetSourceFile = host.getSourceFile;
106+
host.getSourceFile = (fileName) => {
107+
if (fileName === protocolFileName) {
108+
return ts.createSourceFile(fileName, protocolDts, options.target);
109+
}
110+
return originalGetSourceFile.apply(host, [fileName]);
111+
}
112+
const rootFiles = includeTypeScriptServices ? [protocolFileName, typeScriptServicesDts] : [protocolFileName];
113+
return ts.createProgram(rootFiles, options, host);
114+
}
115+
116+
let protocolDts = getInitialDtsFileForProtocol();
117+
const program = getProgramWithProtocolText(protocolDts, /*includeTypeScriptServices*/ true);
118+
119+
const protocolFile = program.getSourceFile("protocol.d.ts");
120+
const extraDeclarations = DeclarationsWalker.getExtraDeclarations(program.getTypeChecker(), protocolFile);
121+
if (extraDeclarations) {
122+
protocolDts += extraDeclarations;
123+
}
124+
// do sanity check and try to compile generated text as standalone program
125+
const sanityCheckProgram = getProgramWithProtocolText(protocolDts, /*includeTypeScriptServices*/ false);
126+
const diagnostics = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics(), ...program.getGlobalDiagnostics()];
127+
if (diagnostics.length) {
128+
const flattenedDiagnostics = diagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, "\n")).join("\n");
129+
throw new Error(`Unexpected errors during sanity check: ${flattenedDiagnostics}`);
130+
}
131+
return protocolDts;
132+
}
133+
134+
if (process.argv.length < 5) {
135+
console.log(`Expected 3 arguments: path to 'protocol.ts', path to 'typescriptservices.d.ts' and path to output file`);
136+
process.exit(1);
137+
}
138+
139+
const protocolTs = process.argv[2];
140+
const typeScriptServicesDts = process.argv[3];
141+
const outputFile = process.argv[4];
142+
const generatedProtocolDts = generateProtocolFile(protocolTs, typeScriptServicesDts);
143+
ts.sys.writeFile(outputFile, generatedProtocolDts);

src/compiler/types.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2574,11 +2574,7 @@ namespace ts {
25742574
NodeJs = 2
25752575
}
25762576

2577-
export type RootPaths = string[];
2578-
export type PathSubstitutions = MapLike<string[]>;
2579-
export type TsConfigOnlyOptions = RootPaths | PathSubstitutions;
2580-
2581-
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | TsConfigOnlyOptions;
2577+
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]>;
25822578

25832579
export interface CompilerOptions {
25842580
allowJs?: boolean;
@@ -2629,14 +2625,14 @@ namespace ts {
26292625
out?: string;
26302626
outDir?: string;
26312627
outFile?: string;
2632-
paths?: PathSubstitutions;
2628+
paths?: MapLike<string[]>;
26332629
preserveConstEnums?: boolean;
26342630
project?: string;
26352631
/* @internal */ pretty?: DiagnosticStyle;
26362632
reactNamespace?: string;
26372633
removeComments?: boolean;
26382634
rootDir?: string;
2639-
rootDirs?: RootPaths;
2635+
rootDirs?: string[];
26402636
skipLibCheck?: boolean;
26412637
skipDefaultLibCheck?: boolean;
26422638
sourceMap?: boolean;

0 commit comments

Comments
 (0)