From 135c6b5d085efad1f73127d629b57e6cd94ed39a Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Fri, 14 Oct 2016 16:36:27 -0700 Subject: [PATCH 1/3] switch enums in protocol to unions of literal types --- scripts/buildProtocol.ts | 68 +++++++++---- src/compiler/commandLineParser.ts | 121 ++++++++++++---------- src/harness/unittests/session.ts | 69 +++++++++++-- src/server/editorServices.ts | 75 ++++++++++++-- src/server/protocol.ts | 163 ++++++++++++++++++++++++++++-- src/server/session.ts | 28 ++--- 6 files changed, 403 insertions(+), 121 deletions(-) diff --git a/scripts/buildProtocol.ts b/scripts/buildProtocol.ts index 55ad086815c68..3154037c746f3 100644 --- a/scripts/buildProtocol.ts +++ b/scripts/buildProtocol.ts @@ -10,6 +10,8 @@ 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) { } @@ -17,9 +19,18 @@ class DeclarationsWalker { 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 { @@ -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); + } } } } @@ -69,15 +79,37 @@ class DeclarationsWalker { case ts.SyntaxKind.Parameter: case ts.SyntaxKind.IndexSignature: if (((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 = (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 (node).types) { + this.processTypeOfNode(t); + } + } + else { + const type = this.typeChecker.getTypeAtLocation(node); + if (type && !(type.flags & (ts.TypeFlags.TypeParameter))) { + this.processType(type); + } + } } } @@ -128,9 +160,11 @@ function generateProtocolFile(protocolTs: string, typeScriptServicesDts: string) if (extraDeclarations) { protocolDts += extraDeclarations; } + protocolDts += "\nimport protocol = ts.server.protocol;"; + protocolDts += "\nexport = 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}`); diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 825519bfb057c..502a3599f32d9 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -7,6 +7,69 @@ namespace ts { /* @internal */ export const compileOnSaveCommandLineOption: CommandLineOption = { name: "compileOnSave", type: "boolean" }; + /* @internal */ + export const jsxCompilerOption: CommandLineOptionOfCustomType = { + name: "jsx", + type: createMap({ + "preserve": JsxEmit.Preserve, + "react": JsxEmit.React + }), + paramType: Diagnostics.KIND, + description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_or_react, + }; + + /* @internal */ + export const moduleCommandLineOption: CommandLineOptionOfCustomType = { + name: "module", + shortName: "m", + type: createMap({ + "none": ModuleKind.None, + "commonjs": ModuleKind.CommonJS, + "amd": ModuleKind.AMD, + "system": ModuleKind.System, + "umd": ModuleKind.UMD, + "es6": ModuleKind.ES6, + "es2015": ModuleKind.ES2015, + }), + description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_system_umd_or_es2015, + paramType: Diagnostics.KIND, + }; + + /* @internal */ + export const newLineCommandLineOption: CommandLineOptionOfCustomType = { + name: "newLine", + type: createMap({ + "crlf": NewLineKind.CarriageReturnLineFeed, + "lf": NewLineKind.LineFeed + }), + description: Diagnostics.Specify_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, + paramType: Diagnostics.NEWLINE, + }; + + /* @internal */ + export const moduleResolutionCommandLineOption: CommandLineOptionOfCustomType = { + name: "moduleResolution", + type: createMap({ + "node": ModuleResolutionKind.NodeJs, + "classic": ModuleResolutionKind.Classic, + }), + description: Diagnostics.Specify_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6, + }; + + /* @internal */ + export const targetCommandLineOption: CommandLineOptionOfCustomType = { + name: "target", + shortName: "t", + type: createMap({ + "es3": ScriptTarget.ES3, + "es5": ScriptTarget.ES5, + "es6": ScriptTarget.ES6, + "es2015": ScriptTarget.ES2015, + }), + description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES2015, + paramType: Diagnostics.VERSION, + }; + /* @internal */ export const optionDeclarations: CommandLineOption[] = [ { @@ -62,15 +125,7 @@ namespace ts { name: "inlineSources", type: "boolean", }, - { - name: "jsx", - type: createMap({ - "preserve": JsxEmit.Preserve, - "react": JsxEmit.React - }), - paramType: Diagnostics.KIND, - description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_or_react, - }, + jsxCompilerOption, { name: "reactNamespace", type: "string", @@ -91,30 +146,8 @@ namespace ts { description: Diagnostics.Specify_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations, paramType: Diagnostics.LOCATION, }, - { - name: "module", - shortName: "m", - type: createMap({ - "none": ModuleKind.None, - "commonjs": ModuleKind.CommonJS, - "amd": ModuleKind.AMD, - "system": ModuleKind.System, - "umd": ModuleKind.UMD, - "es6": ModuleKind.ES6, - "es2015": ModuleKind.ES2015, - }), - description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_system_umd_or_es2015, - paramType: Diagnostics.KIND, - }, - { - name: "newLine", - type: createMap({ - "crlf": NewLineKind.CarriageReturnLineFeed, - "lf": NewLineKind.LineFeed - }), - description: Diagnostics.Specify_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, - paramType: Diagnostics.NEWLINE, - }, + moduleCommandLineOption, + newLineCommandLineOption, { name: "noEmit", type: "boolean", @@ -254,18 +287,7 @@ namespace ts { description: Diagnostics.Do_not_emit_declarations_for_code_that_has_an_internal_annotation, experimental: true }, - { - name: "target", - shortName: "t", - type: createMap({ - "es3": ScriptTarget.ES3, - "es5": ScriptTarget.ES5, - "es6": ScriptTarget.ES6, - "es2015": ScriptTarget.ES2015, - }), - description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES2015, - paramType: Diagnostics.VERSION, - }, + targetCommandLineOption, { name: "version", shortName: "v", @@ -289,14 +311,7 @@ namespace ts { experimental: true, description: Diagnostics.Enables_experimental_support_for_emitting_type_metadata_for_decorators }, - { - name: "moduleResolution", - type: createMap({ - "node": ModuleResolutionKind.NodeJs, - "classic": ModuleResolutionKind.Classic, - }), - description: Diagnostics.Specify_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6, - }, + moduleResolutionCommandLineOption, { name: "allowUnusedLabels", type: "boolean", diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index e2ceb920892e1..de42f01ef5a32 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -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; }; @@ -54,7 +60,7 @@ namespace ts.server { const req: protocol.FileRequest = { command: CommandNames.Open, seq: 0, - type: "command", + type: "request", arguments: { file: undefined } @@ -66,7 +72,7 @@ namespace ts.server { const req: protocol.Request = { command: "foobar", seq: 0, - type: "command" + type: "request" }; session.executeCommand(req); @@ -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: { @@ -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(), + { + module: ModuleKind.System, + target: ScriptTarget.ES5, + jsx: JsxEmit.React, + newLine: NewLineKind.LineFeed, + moduleResolution: ModuleResolutionKind.NodeJs + }); + }); }); describe("onMessage", () => { @@ -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)); @@ -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: { @@ -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`; @@ -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", () => { @@ -303,7 +350,7 @@ namespace ts.server { expect(session.executeCommand({ seq: 0, - type: "command", + type: "request", command: session.customHandler })).to.deep.equal({ response: undefined, @@ -404,7 +451,7 @@ namespace ts.server { this.seq++; this.server.enqueue({ seq: this.seq, - type: "command", + type: "request", command, arguments: args }); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index c0a8b49607774..db0f95709884c 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -17,6 +17,58 @@ namespace ts.server { (event: ProjectServiceEvent): void; } + const indentStyle = createMap({ + "none": IndentStyle.None, + "block": IndentStyle.Block, + "smart": IndentStyle.Smart + }); + + export function convertFormatOptions(protocolOptions: protocol.FormatCodeSettings): FormatCodeSettings { + if (typeof protocolOptions.indentStyle === "string") { + protocolOptions.indentStyle = indentStyle[protocolOptions.indentStyle.toLowerCase()]; + Debug.assert(protocolOptions.indentStyle !== undefined); + } + return protocolOptions; + } + + export function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin { + protocolOptions.target = convertCompilerOptionEnum(targetCommandLineOption, protocolOptions.target); + protocolOptions.module = convertCompilerOptionEnum(moduleCommandLineOption, protocolOptions.module); + protocolOptions.moduleResolution = convertCompilerOptionEnum(moduleResolutionCommandLineOption, protocolOptions.moduleResolution); + protocolOptions.jsx = convertCompilerOptionEnum(jsxCompilerOption, protocolOptions.jsx); + protocolOptions.newLine = convertCompilerOptionEnum(newLineCommandLineOption, protocolOptions.newLine); + return protocolOptions; + } + + function convertCompilerOptionEnum(option: CommandLineOptionOfCustomType, value: string | number): number { + if (typeof value === "string") { + value = option.type[value.toLowerCase()]; + Debug.assert(value !== undefined); + } + return value; + } + + export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind { + return typeof scriptKindName === "string" + ? convertScriptKindName(scriptKindName) + : scriptKindName; + } + + export function convertScriptKindName(scriptKindName: protocol.ScriptKindName) { + switch (scriptKindName) { + case "JS": + return ScriptKind.JS; + case "JSX": + return ScriptKind.JSX; + case "TS": + return ScriptKind.TS; + case "TSX": + return ScriptKind.TSX; + default: + return ScriptKind.Unknown; + } + } + /** * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. */ @@ -63,7 +115,7 @@ namespace ts.server { const externalFilePropertyReader: FilePropertyReader = { getFileName: x => x.fileName, - getScriptKind: x => x.scriptKind, + getScriptKind: x => tryConvertScriptKindName(x.scriptKind), hasMixedContent: x => x.hasMixedContent }; @@ -212,6 +264,10 @@ namespace ts.server { this.ensureInferredProjectsUpToDate(); } + getCompilerOptionsForInferredProjects() { + return this.compilerOptionsForInferredProjects; + } + updateTypingsForProject(response: SetTypings | InvalidateCachedTypings): void { const project = this.findProject(response.projectName); if (!project) { @@ -229,10 +285,10 @@ namespace ts.server { } setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions): void { - this.compilerOptionsForInferredProjects = projectCompilerOptions; + this.compilerOptionsForInferredProjects = convertCompilerOptions(projectCompilerOptions); this.compileOnSaveForInferredProjects = projectCompilerOptions.compileOnSave; for (const proj of this.inferredProjects) { - proj.setCompilerOptions(projectCompilerOptions); + proj.setCompilerOptions(this.compilerOptionsForInferredProjects); proj.compileOnSaveEnabled = projectCompilerOptions.compileOnSave; } this.updateProjectGraphs(this.inferredProjects); @@ -740,12 +796,13 @@ namespace ts.server { } private createAndAddExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typingOptions: TypingOptions) { + const compilerOptions = convertCompilerOptions(options); const project = new ExternalProject( projectFileName, this, this.documentRegistry, - options, - /*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(options, files, externalFilePropertyReader), + compilerOptions, + /*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(compilerOptions, files, externalFilePropertyReader), options.compileOnSave === undefined ? true : options.compileOnSave); this.addFilesToProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typingOptions, /*configFileErrors*/ undefined); @@ -1009,7 +1066,7 @@ namespace ts.server { if (args.file) { const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(args.file)); if (info) { - info.setFormatOptions(args.formatOptions); + info.setFormatOptions(convertFormatOptions(args.formatOptions)); this.logger.info(`Host configuration update for file ${args.file}`); } } @@ -1019,7 +1076,7 @@ namespace ts.server { this.logger.info(`Host information ${args.hostInfo}`); } if (args.formatOptions) { - mergeMaps(this.hostConfiguration.formatCodeOptions, args.formatOptions); + mergeMaps(this.hostConfiguration.formatCodeOptions, convertFormatOptions(args.formatOptions)); this.logger.info("Format host information updated"); } } @@ -1133,7 +1190,7 @@ namespace ts.server { const scriptInfo = this.getScriptInfo(file.fileName); Debug.assert(!scriptInfo || !scriptInfo.isOpen); const normalizedPath = scriptInfo ? scriptInfo.fileName : toNormalizedPath(file.fileName); - this.openClientFileWithNormalizedPath(normalizedPath, file.content, file.scriptKind, file.hasMixedContent); + this.openClientFileWithNormalizedPath(normalizedPath, file.content, tryConvertScriptKindName(file.scriptKind), file.hasMixedContent); } } @@ -1226,7 +1283,7 @@ namespace ts.server { if (externalProject) { if (!tsConfigFiles) { // external project already exists and not config files were added - update the project and return; - this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, proj.options, proj.typingOptions, proj.options.compileOnSave, /*configFileErrors*/ undefined); + this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, convertCompilerOptions(proj.options), proj.typingOptions, proj.options.compileOnSave, /*configFileErrors*/ undefined); return; } // some config files were added to external project (that previously were not there) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 4daf601f85743..bf4f4fdfd8b69 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -102,7 +102,7 @@ namespace ts.server.protocol { /** * One of "request", "response", or "event" */ - type: string; + type: "request" | "response" | "event"; } /** @@ -743,7 +743,7 @@ namespace ts.server.protocol { /** * Script kind of the file */ - scriptKind?: ScriptKind; + scriptKind?: ScriptKindName | ts.ScriptKind; /** * Whether file has mixed content (i.e. .cshtml file that combines html markup with C#/JavaScript) */ @@ -776,20 +776,23 @@ namespace ts.server.protocol { typingOptions?: TypingOptions; } - /** - * For external projects, some of the project settings are sent together with - * compiler settings. - */ - export interface ExternalProjectCompilerOptions extends CompilerOptions { + export interface CompileOnSaveMixin { /** * If compile on save is enabled for the project */ compileOnSave?: boolean; } + /** + * For external projects, some of the project settings are sent together with + * compiler settings. + */ + export type ExternalProjectCompilerOptions = CompilerOptions & CompileOnSaveMixin; + /** * Contains information about current project version */ + /* @internal */ export interface ProjectVersionInfo { /** * Project name @@ -806,7 +809,7 @@ namespace ts.server.protocol { /** * Current set of compiler options for project */ - options: CompilerOptions; + options: ts.CompilerOptions; } /** @@ -830,6 +833,7 @@ namespace ts.server.protocol { * if changes is set - then this is the set of changes that should be applied to existing project * otherwise - assume that nothing is changed */ + /* @internal */ export interface ProjectFiles { /** * Information abount project verison @@ -848,6 +852,7 @@ namespace ts.server.protocol { /** * Combines project information with project level errors. */ + /* @internal */ export interface ProjectFilesWithDiagnostics extends ProjectFiles { /** * List of errors in project @@ -922,9 +927,11 @@ namespace ts.server.protocol { * Used to specify the script kind of the file explicitly. It could be one of the following: * "TS", "JS", "TSX", "JSX" */ - scriptKindName?: "TS" | "JS" | "TSX" | "JSX"; + scriptKindName?: ScriptKindName; } + export type ScriptKindName = "TS" | "JS" | "TSX" | "JSX"; + /** * Open request; value of command field is "open". Notify the * server that the client has file open. The server will not @@ -1019,6 +1026,7 @@ namespace ts.server.protocol { /** * Arguments to SynchronizeProjectListRequest */ + /* @internal */ export interface SynchronizeProjectListRequestArgs { /** * List of last known projects @@ -1939,4 +1947,141 @@ namespace ts.server.protocol { export interface NavTreeResponse extends Response { body?: NavigationTree; } + + export namespace IndentStyle { + export type None = "None"; + export type Block = "Block"; + export type Smart = "Smart"; + } + + export type IndentStyle = IndentStyle.None | IndentStyle.Block | IndentStyle.Smart; + + export interface EditorSettings { + baseIndentSize?: number; + indentSize?: number; + tabSize?: number; + newLineCharacter?: string; + convertTabsToSpaces?: boolean; + indentStyle?: IndentStyle | ts.IndentStyle; + } + + export interface FormatCodeSettings extends EditorSettings { + insertSpaceAfterCommaDelimiter?: boolean; + insertSpaceAfterSemicolonInForStatements?: boolean; + insertSpaceBeforeAndAfterBinaryOperators?: boolean; + insertSpaceAfterKeywordsInControlFlowStatements?: boolean; + insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean; + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean; + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean; + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean; + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + placeOpenBraceOnNewLineForFunctions?: boolean; + placeOpenBraceOnNewLineForControlBlocks?: boolean; + } + + export interface CompilerOptions { + allowJs?: boolean; + allowSyntheticDefaultImports?: boolean; + allowUnreachableCode?: boolean; + allowUnusedLabels?: boolean; + baseUrl?: string; + charset?: string; + declaration?: boolean; + declarationDir?: string; + disableSizeLimit?: boolean; + emitBOM?: boolean; + emitDecoratorMetadata?: boolean; + experimentalDecorators?: boolean; + forceConsistentCasingInFileNames?: boolean; + inlineSourceMap?: boolean; + inlineSources?: boolean; + isolatedModules?: boolean; + jsx?: JsxEmit | ts.JsxEmit; + lib?: string[]; + locale?: string; + mapRoot?: string; + maxNodeModuleJsDepth?: number; + module?: ModuleKind | ts.ModuleKind; + moduleResolution?: ModuleResolutionKind | ts.ModuleResolutionKind; + newLine?: NewLineKind | ts.NewLineKind; + noEmit?: boolean; + noEmitHelpers?: boolean; + noEmitOnError?: boolean; + noErrorTruncation?: boolean; + noFallthroughCasesInSwitch?: boolean; + noImplicitAny?: boolean; + noImplicitReturns?: boolean; + noImplicitThis?: boolean; + noUnusedLocals?: boolean; + noUnusedParameters?: boolean; + noImplicitUseStrict?: boolean; + noLib?: boolean; + noResolve?: boolean; + out?: string; + outDir?: string; + outFile?: string; + paths?: MapLike; + preserveConstEnums?: boolean; + project?: string; + reactNamespace?: string; + removeComments?: boolean; + rootDir?: string; + rootDirs?: string[]; + skipLibCheck?: boolean; + skipDefaultLibCheck?: boolean; + sourceMap?: boolean; + sourceRoot?: string; + strictNullChecks?: boolean; + suppressExcessPropertyErrors?: boolean; + suppressImplicitAnyIndexErrors?: boolean; + target?: ScriptTarget | ts.ScriptTarget; + traceResolution?: boolean; + types?: string[]; + /** Paths used to used to compute primary types search locations */ + typeRoots?: string[]; + [option: string]: CompilerOptionsValue | undefined; + } + + export namespace JsxEmit { + export type None = "None"; + export type Preserve = "Preserve"; + export type React = "React"; + } + + export type JsxEmit = JsxEmit.None | JsxEmit.Preserve | JsxEmit.React; + + export namespace ModuleKind { + export type None = "None"; + export type CommonJS = "CommonJS"; + export type AMD = "AMD"; + export type UMD = "UMD"; + export type System = "System"; + export type ES6 = "ES6"; + export type ES2015 = "ES2015"; + } + + export type ModuleKind = ModuleKind.None | ModuleKind.CommonJS | ModuleKind.AMD | ModuleKind.UMD | ModuleKind.System | ModuleKind.ES6 | ModuleKind.ES2015; + + export namespace ModuleResolutionKind { + export type Classic = "Classic"; + export type Node = "Node"; + } + + export type ModuleResolutionKind = ModuleResolutionKind.Classic | ModuleResolutionKind.Node; + + export namespace NewLineKind { + export type Crlf = "Crlf"; + export type Lf = "Lf"; + } + + export type NewLineKind = NewLineKind.Crlf | NewLineKind.Lf; + + export namespace ScriptTarget { + export type ES3 = "ES3"; + export type ES5 = "ES5"; + export type ES6 = "ES6"; + export type ES2015 = "ES2015"; + } + + export type ScriptTarget = ScriptTarget.ES3 | ScriptTarget.ES5 | ScriptTarget.ES6 | ScriptTarget.ES2015; } diff --git a/src/server/session.ts b/src/server/session.ts index 86864bb57e7eb..238751c0d6387 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -766,7 +766,7 @@ namespace ts.server { private getIndentation(args: protocol.IndentationRequestArgs) { const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file)); - const options = args.options || this.projectService.getFormatCodeOptions(file); + const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file); const indentation = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, options); return { position, indentation }; } @@ -839,19 +839,19 @@ namespace ts.server { private getFormattingEditsForRangeFull(args: protocol.FormatRequestArgs) { const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - const options = args.options || this.projectService.getFormatCodeOptions(file); + const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file); return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsForRange(file, args.position, args.endPosition, options); } private getFormattingEditsForDocumentFull(args: protocol.FormatRequestArgs) { const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - const options = args.options || this.projectService.getFormatCodeOptions(file); + const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file); return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsForDocument(file, options); } private getFormattingEditsAfterKeystrokeFull(args: protocol.FormatOnKeyRequestArgs) { const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - const options = args.options || this.projectService.getFormatCodeOptions(file); + const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file); return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsAfterKeystroke(file, args.position, args.key, options); } @@ -1335,24 +1335,8 @@ namespace ts.server { [CommandNames.RenameInfoFull]: (request: protocol.FileLocationRequest) => { return this.requiredResponse(this.getRenameInfo(request.arguments)); }, - [CommandNames.Open]: (request: protocol.Request) => { - const openArgs = request.arguments; - let scriptKind: ScriptKind; - switch (openArgs.scriptKindName) { - case "TS": - scriptKind = ScriptKind.TS; - break; - case "JS": - scriptKind = ScriptKind.JS; - break; - case "TSX": - scriptKind = ScriptKind.TSX; - break; - case "JSX": - scriptKind = ScriptKind.JSX; - break; - } - this.openClientFile(toNormalizedPath(openArgs.file), openArgs.fileContent, scriptKind); + [CommandNames.Open]: (request: protocol.OpenRequest) => { + this.openClientFile(toNormalizedPath(request.arguments.file), request.arguments.fileContent, convertScriptKindName(request.arguments.scriptKindName)); return this.notRequired(); }, [CommandNames.Quickinfo]: (request: protocol.QuickInfoRequest) => { From 668c3f8f1e3d5146c524999580cef0ce306cc83d Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 17 Oct 2016 14:41:24 -0700 Subject: [PATCH 2/3] addressed PR feedback --- scripts/buildProtocol.ts | 1 + src/compiler/commandLineParser.ts | 120 +++++++++++++----------------- src/server/editorServices.ts | 36 +++++---- 3 files changed, 77 insertions(+), 80 deletions(-) diff --git a/scripts/buildProtocol.ts b/scripts/buildProtocol.ts index 3154037c746f3..66f29f577d933 100644 --- a/scripts/buildProtocol.ts +++ b/scripts/buildProtocol.ts @@ -162,6 +162,7 @@ function generateProtocolFile(protocolTs: string, typeScriptServicesDts: string) } 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 = [...sanityCheckProgram.getSyntacticDiagnostics(), ...sanityCheckProgram.getSemanticDiagnostics(), ...sanityCheckProgram.getGlobalDiagnostics()]; diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 502a3599f32d9..b8fa1423a4d74 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -7,68 +7,6 @@ namespace ts { /* @internal */ export const compileOnSaveCommandLineOption: CommandLineOption = { name: "compileOnSave", type: "boolean" }; - /* @internal */ - export const jsxCompilerOption: CommandLineOptionOfCustomType = { - name: "jsx", - type: createMap({ - "preserve": JsxEmit.Preserve, - "react": JsxEmit.React - }), - paramType: Diagnostics.KIND, - description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_or_react, - }; - - /* @internal */ - export const moduleCommandLineOption: CommandLineOptionOfCustomType = { - name: "module", - shortName: "m", - type: createMap({ - "none": ModuleKind.None, - "commonjs": ModuleKind.CommonJS, - "amd": ModuleKind.AMD, - "system": ModuleKind.System, - "umd": ModuleKind.UMD, - "es6": ModuleKind.ES6, - "es2015": ModuleKind.ES2015, - }), - description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_system_umd_or_es2015, - paramType: Diagnostics.KIND, - }; - - /* @internal */ - export const newLineCommandLineOption: CommandLineOptionOfCustomType = { - name: "newLine", - type: createMap({ - "crlf": NewLineKind.CarriageReturnLineFeed, - "lf": NewLineKind.LineFeed - }), - description: Diagnostics.Specify_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, - paramType: Diagnostics.NEWLINE, - }; - - /* @internal */ - export const moduleResolutionCommandLineOption: CommandLineOptionOfCustomType = { - name: "moduleResolution", - type: createMap({ - "node": ModuleResolutionKind.NodeJs, - "classic": ModuleResolutionKind.Classic, - }), - description: Diagnostics.Specify_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6, - }; - - /* @internal */ - export const targetCommandLineOption: CommandLineOptionOfCustomType = { - name: "target", - shortName: "t", - type: createMap({ - "es3": ScriptTarget.ES3, - "es5": ScriptTarget.ES5, - "es6": ScriptTarget.ES6, - "es2015": ScriptTarget.ES2015, - }), - description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES2015, - paramType: Diagnostics.VERSION, - }; /* @internal */ export const optionDeclarations: CommandLineOption[] = [ @@ -125,7 +63,15 @@ namespace ts { name: "inlineSources", type: "boolean", }, - jsxCompilerOption, + { + name: "jsx", + type: createMap({ + "preserve": JsxEmit.Preserve, + "react": JsxEmit.React + }), + paramType: Diagnostics.KIND, + description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_or_react, + }, { name: "reactNamespace", type: "string", @@ -146,8 +92,30 @@ namespace ts { description: Diagnostics.Specify_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations, paramType: Diagnostics.LOCATION, }, - moduleCommandLineOption, - newLineCommandLineOption, + { + name: "module", + shortName: "m", + type: createMap({ + "none": ModuleKind.None, + "commonjs": ModuleKind.CommonJS, + "amd": ModuleKind.AMD, + "system": ModuleKind.System, + "umd": ModuleKind.UMD, + "es6": ModuleKind.ES6, + "es2015": ModuleKind.ES2015, + }), + description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_system_umd_or_es2015, + paramType: Diagnostics.KIND, + }, + { + name: "newLine", + type: createMap({ + "crlf": NewLineKind.CarriageReturnLineFeed, + "lf": NewLineKind.LineFeed + }), + description: Diagnostics.Specify_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, + paramType: Diagnostics.NEWLINE, + }, { name: "noEmit", type: "boolean", @@ -287,7 +255,18 @@ namespace ts { description: Diagnostics.Do_not_emit_declarations_for_code_that_has_an_internal_annotation, experimental: true }, - targetCommandLineOption, + { + name: "target", + shortName: "t", + type: createMap({ + "es3": ScriptTarget.ES3, + "es5": ScriptTarget.ES5, + "es6": ScriptTarget.ES6, + "es2015": ScriptTarget.ES2015, + }), + description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES2015, + paramType: Diagnostics.VERSION, + }, { name: "version", shortName: "v", @@ -311,7 +290,14 @@ namespace ts { experimental: true, description: Diagnostics.Enables_experimental_support_for_emitting_type_metadata_for_decorators }, - moduleResolutionCommandLineOption, + { + name: "moduleResolution", + type: createMap({ + "node": ModuleResolutionKind.NodeJs, + "classic": ModuleResolutionKind.Classic, + }), + description: Diagnostics.Specify_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6, + }, { name: "allowUnusedLabels", type: "boolean", diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index db0f95709884c..da9fb49779c43 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -17,6 +17,22 @@ namespace ts.server { (event: ProjectServiceEvent): void; } + function prepareConvertersForEnumLikeCompilerOptions(commandLineOptions: CommandLineOption[]): Map> { + const map: Map> = createMap>(); + for (const option of commandLineOptions) { + if (typeof option.type === "object") { + const optionMap = >option.type; + // verify that map contains only numbers + for (const id in optionMap) { + Debug.assert(typeof optionMap[id] === "number"); + } + map[option.name] = optionMap; + } + } + return map; + } + + const compilerOptionConverters = prepareConvertersForEnumLikeCompilerOptions(optionDeclarations); const indentStyle = createMap({ "none": IndentStyle.None, "block": IndentStyle.Block, @@ -32,20 +48,14 @@ namespace ts.server { } export function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin { - protocolOptions.target = convertCompilerOptionEnum(targetCommandLineOption, protocolOptions.target); - protocolOptions.module = convertCompilerOptionEnum(moduleCommandLineOption, protocolOptions.module); - protocolOptions.moduleResolution = convertCompilerOptionEnum(moduleResolutionCommandLineOption, protocolOptions.moduleResolution); - protocolOptions.jsx = convertCompilerOptionEnum(jsxCompilerOption, protocolOptions.jsx); - protocolOptions.newLine = convertCompilerOptionEnum(newLineCommandLineOption, protocolOptions.newLine); - return protocolOptions; - } - - function convertCompilerOptionEnum(option: CommandLineOptionOfCustomType, value: string | number): number { - if (typeof value === "string") { - value = option.type[value.toLowerCase()]; - Debug.assert(value !== undefined); + for (const id in compilerOptionConverters) { + const propertyValue = protocolOptions[id]; + if (typeof propertyValue === "string") { + const mappedValues = compilerOptionConverters[id]; + protocolOptions[id] = mappedValues[propertyValue.toLowerCase()]; + } } - return value; + return protocolOptions; } export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind { From 8d84245ba295097a0003e69c1d696e57aa236be6 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 17 Oct 2016 14:50:21 -0700 Subject: [PATCH 3/3] remove changes in commandLineParser.ts --- src/compiler/commandLineParser.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index b8fa1423a4d74..825519bfb057c 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -7,7 +7,6 @@ namespace ts { /* @internal */ export const compileOnSaveCommandLineOption: CommandLineOption = { name: "compileOnSave", type: "boolean" }; - /* @internal */ export const optionDeclarations: CommandLineOption[] = [ {