Skip to content

Commit 05ebd1d

Browse files
committed
Merge pull request microsoft#11651 from Microsoft/vladima/literals-in-protocol
switch enums in protocol to unions of literal types
1 parent 039c6e5 commit 05ebd1d

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
@@ -39,12 +39,18 @@ namespace ts.server {
3939
getLogFileName: (): string => undefined
4040
};
4141

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

4652
beforeEach(() => {
47-
session = new Session(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true);
53+
session = new TestSession(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true);
4854
session.send = (msg: protocol.Message) => {
4955
lastSent = msg;
5056
};
@@ -55,7 +61,7 @@ namespace ts.server {
5561
const req: protocol.FileRequest = {
5662
command: CommandNames.Open,
5763
seq: 0,
58-
type: "command",
64+
type: "request",
5965
arguments: {
6066
file: undefined
6167
}
@@ -67,7 +73,7 @@ namespace ts.server {
6773
const req: protocol.Request = {
6874
command: "foobar",
6975
seq: 0,
70-
type: "command"
76+
type: "request"
7177
};
7278

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

111158
describe("onMessage", () => {
@@ -118,7 +165,7 @@ namespace ts.server {
118165
const req: protocol.Request = {
119166
command: name,
120167
seq: i,
121-
type: "command"
168+
type: "request"
122169
};
123170
i++;
124171
session.onMessage(JSON.stringify(req));
@@ -151,7 +198,7 @@ namespace ts.server {
151198
const req: protocol.ConfigureRequest = {
152199
command: CommandNames.Configure,
153200
seq: 0,
154-
type: "command",
201+
type: "request",
155202
arguments: {
156203
hostInfo: "unit test",
157204
formatOptions: {
@@ -175,7 +222,7 @@ namespace ts.server {
175222

176223
describe("send", () => {
177224
it("is an overrideable handle which sends protocol messages over the wire", () => {
178-
const msg = { seq: 0, type: "none" };
225+
const msg: server.protocol.Request = { seq: 0, type: "request", command: "" };
179226
const strmsg = JSON.stringify(msg);
180227
const len = 1 + Utils.byteLength(strmsg, "utf8");
181228
const resultMsg = `Content-Length: ${len}\r\n\r\n${strmsg}\n`;
@@ -203,7 +250,7 @@ namespace ts.server {
203250
expect(session.executeCommand({
204251
command,
205252
seq: 0,
206-
type: "command"
253+
type: "request"
207254
})).to.deep.equal(result);
208255
});
209256
it("throws when a duplicate handler is passed", () => {
@@ -304,7 +351,7 @@ namespace ts.server {
304351

305352
expect(session.executeCommand({
306353
seq: 0,
307-
type: "command",
354+
type: "request",
308355
command: session.customHandler
309356
})).to.deep.equal({
310357
response: undefined,
@@ -405,7 +452,7 @@ namespace ts.server {
405452
this.seq++;
406453
this.server.enqueue({
407454
seq: this.seq,
408-
type: "command",
455+
type: "request",
409456
command,
410457
arguments: args
411458
});

0 commit comments

Comments
 (0)