Skip to content

Commit ea3cbfb

Browse files
authored
Merge pull request #11651 from Microsoft/vladima/literals-in-protocol
switch enums in protocol to unions of literal types
2 parents 1635679 + 8d84245 commit ea3cbfb

File tree

5 files changed

+346
-68
lines changed

5 files changed

+346
-68
lines changed

scripts/buildProtocol.ts

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,27 @@ function endsWith(s: string, suffix: string) {
1010
class DeclarationsWalker {
1111
private visitedTypes: ts.Type[] = [];
1212
private text = "";
13+
private removedTypes: ts.Type[] = [];
14+
1315
private constructor(private typeChecker: ts.TypeChecker, private protocolFile: ts.SourceFile) {
1416
}
1517

1618
static getExtraDeclarations(typeChecker: ts.TypeChecker, protocolFile: ts.SourceFile): string {
1719
let text = "declare namespace ts.server.protocol {\n";
1820
var walker = new DeclarationsWalker(typeChecker, protocolFile);
1921
walker.visitTypeNodes(protocolFile);
20-
return walker.text
22+
text = walker.text
2123
? `declare namespace ts.server.protocol {\n${walker.text}}`
2224
: "";
25+
if (walker.removedTypes) {
26+
text += "\ndeclare namespace ts {\n";
27+
text += " // these types are empty stubs for types from services and should not be used directly\n"
28+
for (const type of walker.removedTypes) {
29+
text += ` export type ${type.symbol.name} = never;\n`;
30+
}
31+
text += "}"
32+
}
33+
return text;
2334
}
2435

2536
private processType(type: ts.Type): void {
@@ -41,19 +52,18 @@ class DeclarationsWalker {
4152
if (sourceFile === this.protocolFile || path.basename(sourceFile.fileName) === "lib.d.ts") {
4253
return;
4354
}
44-
// splice declaration in final d.ts file
45-
let text = decl.getFullText();
46-
if (decl.kind === ts.SyntaxKind.EnumDeclaration && !(decl.flags & ts.NodeFlags.Const)) {
47-
// patch enum declaration to make them constan
48-
const declStart = decl.getStart() - decl.getFullStart();
49-
const prefix = text.substring(0, declStart);
50-
const suffix = text.substring(declStart + "enum".length, decl.getEnd() - decl.getFullStart());
51-
text = prefix + "const enum" + suffix;
55+
if (decl.kind === ts.SyntaxKind.EnumDeclaration) {
56+
this.removedTypes.push(type);
57+
return;
5258
}
53-
this.text += `${text}\n`;
59+
else {
60+
// splice declaration in final d.ts file
61+
let text = decl.getFullText();
62+
this.text += `${text}\n`;
63+
// recursively pull all dependencies into result dts file
5464

55-
// recursively pull all dependencies into result dts file
56-
this.visitTypeNodes(decl);
65+
this.visitTypeNodes(decl);
66+
}
5767
}
5868
}
5969
}
@@ -69,15 +79,37 @@ class DeclarationsWalker {
6979
case ts.SyntaxKind.Parameter:
7080
case ts.SyntaxKind.IndexSignature:
7181
if (((<ts.VariableDeclaration | ts.MethodDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration | ts.PropertySignature | ts.MethodSignature | ts.IndexSignatureDeclaration>node.parent).type) === node) {
72-
const type = this.typeChecker.getTypeAtLocation(node);
73-
if (type && !(type.flags & ts.TypeFlags.TypeParameter)) {
74-
this.processType(type);
75-
}
82+
this.processTypeOfNode(node);
7683
}
7784
break;
85+
case ts.SyntaxKind.InterfaceDeclaration:
86+
const heritageClauses = (<ts.InterfaceDeclaration>node.parent).heritageClauses;
87+
if (heritageClauses) {
88+
if (heritageClauses[0].token !== ts.SyntaxKind.ExtendsKeyword) {
89+
throw new Error(`Unexpected kind of heritage clause: ${ts.SyntaxKind[heritageClauses[0].kind]}`);
90+
}
91+
for (const type of heritageClauses[0].types) {
92+
this.processTypeOfNode(type);
93+
}
94+
}
95+
break;
7896
}
7997
}
8098
ts.forEachChild(node, n => this.visitTypeNodes(n));
99+
}
100+
101+
private processTypeOfNode(node: ts.Node): void {
102+
if (node.kind === ts.SyntaxKind.UnionType) {
103+
for (const t of (<ts.UnionTypeNode>node).types) {
104+
this.processTypeOfNode(t);
105+
}
106+
}
107+
else {
108+
const type = this.typeChecker.getTypeAtLocation(node);
109+
if (type && !(type.flags & (ts.TypeFlags.TypeParameter))) {
110+
this.processType(type);
111+
}
112+
}
81113
}
82114
}
83115

@@ -128,9 +160,12 @@ function generateProtocolFile(protocolTs: string, typeScriptServicesDts: string)
128160
if (extraDeclarations) {
129161
protocolDts += extraDeclarations;
130162
}
163+
protocolDts += "\nimport protocol = ts.server.protocol;";
164+
protocolDts += "\nexport = protocol;";
165+
protocolDts += "\nexport as namespace protocol;";
131166
// do sanity check and try to compile generated text as standalone program
132167
const sanityCheckProgram = getProgramWithProtocolText(protocolDts, /*includeTypeScriptServices*/ false);
133-
const diagnostics = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics(), ...program.getGlobalDiagnostics()];
168+
const diagnostics = [...sanityCheckProgram.getSyntacticDiagnostics(), ...sanityCheckProgram.getSemanticDiagnostics(), ...sanityCheckProgram.getGlobalDiagnostics()];
134169
if (diagnostics.length) {
135170
const flattenedDiagnostics = diagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, "\n")).join("\n");
136171
throw new Error(`Unexpected errors during sanity check: ${flattenedDiagnostics}`);

src/harness/unittests/session.ts

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,18 @@ namespace ts.server {
3838
getLogFileName: (): string => undefined
3939
};
4040

41+
class TestSession extends Session {
42+
getProjectService() {
43+
return this.projectService;
44+
}
45+
}
46+
4147
describe("the Session class", () => {
42-
let session: Session;
48+
let session: TestSession;
4349
let lastSent: protocol.Message;
4450

4551
beforeEach(() => {
46-
session = new Session(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true);
52+
session = new TestSession(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true);
4753
session.send = (msg: protocol.Message) => {
4854
lastSent = msg;
4955
};
@@ -54,7 +60,7 @@ namespace ts.server {
5460
const req: protocol.FileRequest = {
5561
command: CommandNames.Open,
5662
seq: 0,
57-
type: "command",
63+
type: "request",
5864
arguments: {
5965
file: undefined
6066
}
@@ -66,7 +72,7 @@ namespace ts.server {
6672
const req: protocol.Request = {
6773
command: "foobar",
6874
seq: 0,
69-
type: "command"
75+
type: "request"
7076
};
7177

7278
session.executeCommand(req);
@@ -84,7 +90,7 @@ namespace ts.server {
8490
const req: protocol.ConfigureRequest = {
8591
command: CommandNames.Configure,
8692
seq: 0,
87-
type: "command",
93+
type: "request",
8894
arguments: {
8995
hostInfo: "unit test",
9096
formatOptions: {
@@ -105,6 +111,47 @@ namespace ts.server {
105111
body: undefined
106112
});
107113
});
114+
it ("should handle literal types in request", () => {
115+
const configureRequest: protocol.ConfigureRequest = {
116+
command: CommandNames.Configure,
117+
seq: 0,
118+
type: "request",
119+
arguments: {
120+
formatOptions: {
121+
indentStyle: "Block"
122+
}
123+
}
124+
};
125+
126+
session.onMessage(JSON.stringify(configureRequest));
127+
128+
assert.equal(session.getProjectService().getFormatCodeOptions().indentStyle, IndentStyle.Block);
129+
130+
const setOptionsRequest: protocol.SetCompilerOptionsForInferredProjectsRequest = {
131+
command: CommandNames.CompilerOptionsForInferredProjects,
132+
seq: 1,
133+
type: "request",
134+
arguments: {
135+
options: {
136+
module: "System",
137+
target: "ES5",
138+
jsx: "React",
139+
newLine: "Lf",
140+
moduleResolution: "Node"
141+
}
142+
}
143+
};
144+
session.onMessage(JSON.stringify(setOptionsRequest));
145+
assert.deepEqual(
146+
session.getProjectService().getCompilerOptionsForInferredProjects(),
147+
<CompilerOptions>{
148+
module: ModuleKind.System,
149+
target: ScriptTarget.ES5,
150+
jsx: JsxEmit.React,
151+
newLine: NewLineKind.LineFeed,
152+
moduleResolution: ModuleResolutionKind.NodeJs
153+
});
154+
});
108155
});
109156

110157
describe("onMessage", () => {
@@ -117,7 +164,7 @@ namespace ts.server {
117164
const req: protocol.Request = {
118165
command: name,
119166
seq: i,
120-
type: "command"
167+
type: "request"
121168
};
122169
i++;
123170
session.onMessage(JSON.stringify(req));
@@ -150,7 +197,7 @@ namespace ts.server {
150197
const req: protocol.ConfigureRequest = {
151198
command: CommandNames.Configure,
152199
seq: 0,
153-
type: "command",
200+
type: "request",
154201
arguments: {
155202
hostInfo: "unit test",
156203
formatOptions: {
@@ -174,7 +221,7 @@ namespace ts.server {
174221

175222
describe("send", () => {
176223
it("is an overrideable handle which sends protocol messages over the wire", () => {
177-
const msg = { seq: 0, type: "none" };
224+
const msg: server.protocol.Request = { seq: 0, type: "request", command: "" };
178225
const strmsg = JSON.stringify(msg);
179226
const len = 1 + Utils.byteLength(strmsg, "utf8");
180227
const resultMsg = `Content-Length: ${len}\r\n\r\n${strmsg}\n`;
@@ -202,7 +249,7 @@ namespace ts.server {
202249
expect(session.executeCommand({
203250
command,
204251
seq: 0,
205-
type: "command"
252+
type: "request"
206253
})).to.deep.equal(result);
207254
});
208255
it("throws when a duplicate handler is passed", () => {
@@ -303,7 +350,7 @@ namespace ts.server {
303350

304351
expect(session.executeCommand({
305352
seq: 0,
306-
type: "command",
353+
type: "request",
307354
command: session.customHandler
308355
})).to.deep.equal({
309356
response: undefined,
@@ -404,7 +451,7 @@ namespace ts.server {
404451
this.seq++;
405452
this.server.enqueue({
406453
seq: this.seq,
407-
type: "command",
454+
type: "request",
408455
command,
409456
arguments: args
410457
});

0 commit comments

Comments
 (0)