Skip to content

Commit

Permalink
Merge pull request #11651 from Microsoft/vladima/literals-in-protocol
Browse files Browse the repository at this point in the history
switch enums in protocol to unions of literal types
  • Loading branch information
vladima authored Oct 17, 2016
2 parents 1635679 + 8d84245 commit ea3cbfb
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 68 deletions.
69 changes: 52 additions & 17 deletions scripts/buildProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,27 @@ function endsWith(s: string, suffix: string) {
class DeclarationsWalker {
private visitedTypes: ts.Type[] = [];
private text = "";
private removedTypes: ts.Type[] = [];

private constructor(private typeChecker: ts.TypeChecker, private protocolFile: ts.SourceFile) {
}

static getExtraDeclarations(typeChecker: ts.TypeChecker, protocolFile: ts.SourceFile): string {
let text = "declare namespace ts.server.protocol {\n";
var walker = new DeclarationsWalker(typeChecker, protocolFile);
walker.visitTypeNodes(protocolFile);
return walker.text
text = walker.text
? `declare namespace ts.server.protocol {\n${walker.text}}`
: "";
if (walker.removedTypes) {
text += "\ndeclare namespace ts {\n";
text += " // these types are empty stubs for types from services and should not be used directly\n"
for (const type of walker.removedTypes) {
text += ` export type ${type.symbol.name} = never;\n`;
}
text += "}"
}
return text;
}

private processType(type: ts.Type): void {
Expand All @@ -41,19 +52,18 @@ class DeclarationsWalker {
if (sourceFile === this.protocolFile || path.basename(sourceFile.fileName) === "lib.d.ts") {
return;
}
// splice declaration in final d.ts file
let text = decl.getFullText();
if (decl.kind === ts.SyntaxKind.EnumDeclaration && !(decl.flags & ts.NodeFlags.Const)) {
// patch enum declaration to make them constan
const declStart = decl.getStart() - decl.getFullStart();
const prefix = text.substring(0, declStart);
const suffix = text.substring(declStart + "enum".length, decl.getEnd() - decl.getFullStart());
text = prefix + "const enum" + suffix;
if (decl.kind === ts.SyntaxKind.EnumDeclaration) {
this.removedTypes.push(type);
return;
}
this.text += `${text}\n`;
else {
// splice declaration in final d.ts file
let text = decl.getFullText();
this.text += `${text}\n`;
// recursively pull all dependencies into result dts file

// recursively pull all dependencies into result dts file
this.visitTypeNodes(decl);
this.visitTypeNodes(decl);
}
}
}
}
Expand All @@ -69,15 +79,37 @@ class DeclarationsWalker {
case ts.SyntaxKind.Parameter:
case ts.SyntaxKind.IndexSignature:
if (((<ts.VariableDeclaration | ts.MethodDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration | ts.PropertySignature | ts.MethodSignature | ts.IndexSignatureDeclaration>node.parent).type) === node) {
const type = this.typeChecker.getTypeAtLocation(node);
if (type && !(type.flags & ts.TypeFlags.TypeParameter)) {
this.processType(type);
}
this.processTypeOfNode(node);
}
break;
case ts.SyntaxKind.InterfaceDeclaration:
const heritageClauses = (<ts.InterfaceDeclaration>node.parent).heritageClauses;
if (heritageClauses) {
if (heritageClauses[0].token !== ts.SyntaxKind.ExtendsKeyword) {
throw new Error(`Unexpected kind of heritage clause: ${ts.SyntaxKind[heritageClauses[0].kind]}`);
}
for (const type of heritageClauses[0].types) {
this.processTypeOfNode(type);
}
}
break;
}
}
ts.forEachChild(node, n => this.visitTypeNodes(n));
}

private processTypeOfNode(node: ts.Node): void {
if (node.kind === ts.SyntaxKind.UnionType) {
for (const t of (<ts.UnionTypeNode>node).types) {
this.processTypeOfNode(t);
}
}
else {
const type = this.typeChecker.getTypeAtLocation(node);
if (type && !(type.flags & (ts.TypeFlags.TypeParameter))) {
this.processType(type);
}
}
}
}

Expand Down Expand Up @@ -128,9 +160,12 @@ function generateProtocolFile(protocolTs: string, typeScriptServicesDts: string)
if (extraDeclarations) {
protocolDts += extraDeclarations;
}
protocolDts += "\nimport protocol = ts.server.protocol;";
protocolDts += "\nexport = protocol;";
protocolDts += "\nexport as namespace protocol;";
// do sanity check and try to compile generated text as standalone program
const sanityCheckProgram = getProgramWithProtocolText(protocolDts, /*includeTypeScriptServices*/ false);
const diagnostics = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics(), ...program.getGlobalDiagnostics()];
const diagnostics = [...sanityCheckProgram.getSyntacticDiagnostics(), ...sanityCheckProgram.getSemanticDiagnostics(), ...sanityCheckProgram.getGlobalDiagnostics()];
if (diagnostics.length) {
const flattenedDiagnostics = diagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, "\n")).join("\n");
throw new Error(`Unexpected errors during sanity check: ${flattenedDiagnostics}`);
Expand Down
69 changes: 58 additions & 11 deletions src/harness/unittests/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,18 @@ namespace ts.server {
getLogFileName: (): string => undefined
};

class TestSession extends Session {
getProjectService() {
return this.projectService;
}
}

describe("the Session class", () => {
let session: Session;
let session: TestSession;
let lastSent: protocol.Message;

beforeEach(() => {
session = new Session(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true);
session = new TestSession(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true);
session.send = (msg: protocol.Message) => {
lastSent = msg;
};
Expand All @@ -54,7 +60,7 @@ namespace ts.server {
const req: protocol.FileRequest = {
command: CommandNames.Open,
seq: 0,
type: "command",
type: "request",
arguments: {
file: undefined
}
Expand All @@ -66,7 +72,7 @@ namespace ts.server {
const req: protocol.Request = {
command: "foobar",
seq: 0,
type: "command"
type: "request"
};

session.executeCommand(req);
Expand All @@ -84,7 +90,7 @@ namespace ts.server {
const req: protocol.ConfigureRequest = {
command: CommandNames.Configure,
seq: 0,
type: "command",
type: "request",
arguments: {
hostInfo: "unit test",
formatOptions: {
Expand All @@ -105,6 +111,47 @@ namespace ts.server {
body: undefined
});
});
it ("should handle literal types in request", () => {
const configureRequest: protocol.ConfigureRequest = {
command: CommandNames.Configure,
seq: 0,
type: "request",
arguments: {
formatOptions: {
indentStyle: "Block"
}
}
};

session.onMessage(JSON.stringify(configureRequest));

assert.equal(session.getProjectService().getFormatCodeOptions().indentStyle, IndentStyle.Block);

const setOptionsRequest: protocol.SetCompilerOptionsForInferredProjectsRequest = {
command: CommandNames.CompilerOptionsForInferredProjects,
seq: 1,
type: "request",
arguments: {
options: {
module: "System",
target: "ES5",
jsx: "React",
newLine: "Lf",
moduleResolution: "Node"
}
}
};
session.onMessage(JSON.stringify(setOptionsRequest));
assert.deepEqual(
session.getProjectService().getCompilerOptionsForInferredProjects(),
<CompilerOptions>{
module: ModuleKind.System,
target: ScriptTarget.ES5,
jsx: JsxEmit.React,
newLine: NewLineKind.LineFeed,
moduleResolution: ModuleResolutionKind.NodeJs
});
});
});

describe("onMessage", () => {
Expand All @@ -117,7 +164,7 @@ namespace ts.server {
const req: protocol.Request = {
command: name,
seq: i,
type: "command"
type: "request"
};
i++;
session.onMessage(JSON.stringify(req));
Expand Down Expand Up @@ -150,7 +197,7 @@ namespace ts.server {
const req: protocol.ConfigureRequest = {
command: CommandNames.Configure,
seq: 0,
type: "command",
type: "request",
arguments: {
hostInfo: "unit test",
formatOptions: {
Expand All @@ -174,7 +221,7 @@ namespace ts.server {

describe("send", () => {
it("is an overrideable handle which sends protocol messages over the wire", () => {
const msg = { seq: 0, type: "none" };
const msg: server.protocol.Request = { seq: 0, type: "request", command: "" };
const strmsg = JSON.stringify(msg);
const len = 1 + Utils.byteLength(strmsg, "utf8");
const resultMsg = `Content-Length: ${len}\r\n\r\n${strmsg}\n`;
Expand Down Expand Up @@ -202,7 +249,7 @@ namespace ts.server {
expect(session.executeCommand({
command,
seq: 0,
type: "command"
type: "request"
})).to.deep.equal(result);
});
it("throws when a duplicate handler is passed", () => {
Expand Down Expand Up @@ -303,7 +350,7 @@ namespace ts.server {

expect(session.executeCommand({
seq: 0,
type: "command",
type: "request",
command: session.customHandler
})).to.deep.equal({
response: undefined,
Expand Down Expand Up @@ -404,7 +451,7 @@ namespace ts.server {
this.seq++;
this.server.enqueue({
seq: this.seq,
type: "command",
type: "request",
command,
arguments: args
});
Expand Down
Loading

0 comments on commit ea3cbfb

Please sign in to comment.