From a98cca77234583adc13ae7e0b40ba4e5baea9d99 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 25 Sep 2014 13:10:04 -0700 Subject: [PATCH] Properly support string-literal property names and escape external module names. --- src/compiler/core.ts | 23 +++++++++ src/compiler/emitter.ts | 23 --------- .../getScriptLexicalStructureWalker.ts | 50 +++++++++++++------ ...ptLexicalStructureItemsExternalModules3.ts | 15 ++++++ .../scriptLexicalStructureModules.ts | 2 +- ...icalStructureMultilineStringIdentifiers.ts | 40 +++++++++++++++ 6 files changed, 113 insertions(+), 40 deletions(-) create mode 100644 tests/cases/fourslash/scriptLexicalStructureItemsExternalModules3.ts create mode 100644 tests/cases/fourslash/scriptLexicalStructureMultilineStringIdentifiers.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 2ca50ae4923..d95bc4f89ce 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -535,6 +535,29 @@ module ts { return path; } + var escapedCharsRegExp = /[\t\v\f\b\0\r\n\"\\\u2028\u2029\u0085]/g; + var escapedCharsMap: Map = { + "\t": "\\t", + "\v": "\\v", + "\f": "\\f", + "\b": "\\b", + "\0": "\\0", + "\r": "\\r", + "\n": "\\n", + "\"": "\\\"", + "\u2028": "\\u2028", // lineSeparator + "\u2029": "\\u2029", // paragraphSeparator + "\u0085": "\\u0085" // nextLine + }; + + /** NOTE: This *does not* support the full escape characters, it only supports the subset that can be used in file names + * or string literals. If the information encoded in the map changes, this needs to be revisited. */ + export function escapeString(s: string): string { + return escapedCharsRegExp.test(s) ? s.replace(escapedCharsRegExp, c => { + return escapedCharsMap[c] || c; + }) : s; + } + export interface ObjectAllocator { getNodeConstructor(kind: SyntaxKind): new () => Node; getSymbolConstructor(): new (flags: SymbolFlags, name: string) => Symbol; diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 74b0533e5b4..793660763a9 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -591,21 +591,6 @@ module ts { recordSourceMapSpan(comment.end); } - var escapedCharsRegExp = /[\t\v\f\b\0\r\n\"\u2028\u2029\u0085]/g; - var escapedCharsMap: Map = { - "\t": "\\t", - "\v": "\\v", - "\f": "\\f", - "\b": "\\b", - "\0": "\\0", - "\r": "\\r", - "\n": "\\n", - "\"": "\\\"", - "\u2028": "\\u2028", // lineSeparator - "\u2029": "\\u2029", // paragraphSeparator - "\u0085": "\\u0085" // nextLine - }; - function serializeSourceMapContents(version: number, file: string, sourceRoot: string, sources: string[], names: string[], mappings: string) { if (typeof JSON !== "undefined") { return JSON.stringify({ @@ -620,14 +605,6 @@ module ts { return "{\"version\":" + version + ",\"file\":\"" + escapeString(file) + "\",\"sourceRoot\":\"" + escapeString(sourceRoot) + "\",\"sources\":[" + serializeStringArray(sources) + "],\"names\":[" + serializeStringArray(names) + "],\"mappings\":\"" + escapeString(mappings) + "\"}"; - /** This does not support the full escape characters, it only supports the subset that can be used in file names - * or string literals. If the information encoded in the map changes, this needs to be revisited. */ - function escapeString(s: string): string { - return escapedCharsRegExp.test(s) ? s.replace(escapedCharsRegExp, c => { - return escapedCharsMap[c] || c; - }) : s; - } - function serializeStringArray(list: string[]): string { var output = ""; for (var i = 0, n = list.length; i < n; i++) { diff --git a/src/services/getScriptLexicalStructureWalker.ts b/src/services/getScriptLexicalStructureWalker.ts index 7013460598f..c6158bfead7 100644 --- a/src/services/getScriptLexicalStructureWalker.ts +++ b/src/services/getScriptLexicalStructureWalker.ts @@ -151,23 +151,23 @@ module ts { return basicChildItem(node, parameter.name.text, ts.ScriptElementKind.memberVariableElement); case SyntaxKind.Method: - var memberFunction = node; - return basicChildItem(node, memberFunction.name.text, ts.ScriptElementKind.memberFunctionElement); + var method = node; + return basicChildItem(node, getPropertyText(method.name), ts.ScriptElementKind.memberFunctionElement); case SyntaxKind.GetAccessor: var getAccessor = node; - return basicChildItem(node, getAccessor.name.text, ts.ScriptElementKind.memberGetAccessorElement); + return basicChildItem(node, getPropertyText(getAccessor.name), ts.ScriptElementKind.memberGetAccessorElement); case SyntaxKind.SetAccessor: var setAccessor = node; - return basicChildItem(node, setAccessor.name.text, ts.ScriptElementKind.memberSetAccessorElement); + return basicChildItem(node, getPropertyText(setAccessor.name), ts.ScriptElementKind.memberSetAccessorElement); case SyntaxKind.IndexSignature: return basicChildItem(node, "[]", ts.ScriptElementKind.indexSignatureElement); case SyntaxKind.EnumMember: - var enumElement = node; - return basicChildItem(node, enumElement.name.text, ts.ScriptElementKind.memberVariableElement); + var enumMember = node; + return basicChildItem(node, getPropertyText(enumMember.name), ts.ScriptElementKind.memberVariableElement); case SyntaxKind.CallSignature: return basicChildItem(node, "()", ts.ScriptElementKind.callSignatureElement); @@ -176,8 +176,8 @@ module ts { return basicChildItem(node, "new()", ts.ScriptElementKind.constructSignatureElement); case SyntaxKind.Property: - var propertySignature = node; - return basicChildItem(node, propertySignature.name.text, ts.ScriptElementKind.memberVariableElement); + var property = node; + return basicChildItem(node, getPropertyText(property.name), ts.ScriptElementKind.memberVariableElement); case SyntaxKind.FunctionDeclaration: var functionDeclaration = node; @@ -224,13 +224,13 @@ module ts { return undefined; - function getModuleNames(moduleDeclaration: ModuleDeclaration): string[]{ + function getModuleName(moduleDeclaration: ModuleDeclaration): string { // We want to maintain quotation marks. if (moduleDeclaration.name.kind === SyntaxKind.StringLiteral) { - return [getSourceTextOfNode(moduleDeclaration.name)]; + return getPropertyText(moduleDeclaration.name); } - // Otherwise, we need to aggregate each identifier of the qualified name. + // Otherwise, we need to aggregate each identifier to build up the qualified name. var result: string[] = []; result.push(moduleDeclaration.name.text); @@ -241,15 +241,15 @@ module ts { result.push(moduleDeclaration.name.text); } - return result; + return result.join("."); } function createModuleItem(node: ModuleDeclaration): NavigationBarItem { - var moduleNames = getModuleNames(node); + var moduleName = getModuleName(node); var childItems = getItemsWorker(getChildNodes((getInnermostModule(node).body).statements), createChildItem); - return new ts.NavigationBarItem(moduleNames.join("."), + return new ts.NavigationBarItem(moduleName, ts.ScriptElementKind.moduleElement, getNodeModifiers(node), [getNodeSpan(node)], @@ -281,7 +281,7 @@ module ts { hasGlobalNode = true; var rootName = isExternalModule(node) ? - "\"" + getBaseFilename(removeFileExtension(normalizePath(node.filename))) + "\"" : + "\"" + escapeString(getBaseFilename(removeFileExtension(normalizePath(node.filename)))) + "\"" : "" return new ts.NavigationBarItem(rootName, @@ -340,7 +340,6 @@ module ts { } function getInnermostModule(node: ModuleDeclaration): ModuleDeclaration { - while (node.body.kind === SyntaxKind.ModuleDeclaration) { node = node.body; } @@ -351,5 +350,24 @@ module ts { function getNodeSpan(node: Node) { return TypeScript.TextSpan.fromBounds(node.getStart(), node.getEnd()); } + + function getPropertyText(node: Node): string { + if (node.kind === SyntaxKind.Identifier) { + return (node).text; + } + + if (node.kind === SyntaxKind.StringLiteral) { + // normalize the quotes and remove all '\{newline}'s from the original text. + var text = getSourceTextOfNodeFromSourceText(sourceFile.text, node); + text = text.substring(1, text.length - 1).replace(/\\\r?\n/g, ""); + return "\"" + text + "\""; + } + + if (node.kind === SyntaxKind.NumericLiteral) { + return (node).text; + } + + Debug.fail("getPropertyText given a property that is neither an identifier nor a literal expression."); + } } } \ No newline at end of file diff --git a/tests/cases/fourslash/scriptLexicalStructureItemsExternalModules3.ts b/tests/cases/fourslash/scriptLexicalStructureItemsExternalModules3.ts new file mode 100644 index 00000000000..9ef49e38776 --- /dev/null +++ b/tests/cases/fourslash/scriptLexicalStructureItemsExternalModules3.ts @@ -0,0 +1,15 @@ +/// + +// @Filename: test/my fil"e.ts +////{| "itemName": "Bar", "kind": "class" |}export class Bar { +//// {| "itemName": "s", "kind": "property", "parentName": "Bar" |}public s: string; +////} +////{| "itemName": "\"my fil\\\"e\"", "kind": "module" |} +////{| "itemName": "x", "kind": "var", "parentName": "\"file\"" |} +////export var x: number; + +test.markers().forEach((marker) => { + verify.getScriptLexicalStructureListContains(marker.data.itemName, marker.data.kind, marker.fileName, marker.data.parentName); +}); + +verify.getScriptLexicalStructureListCount(4); // external module node + variable in module + class + property diff --git a/tests/cases/fourslash/scriptLexicalStructureModules.ts b/tests/cases/fourslash/scriptLexicalStructureModules.ts index ee0f91cc73c..602cc170dee 100644 --- a/tests/cases/fourslash/scriptLexicalStructureModules.ts +++ b/tests/cases/fourslash/scriptLexicalStructureModules.ts @@ -3,7 +3,7 @@ ////declare module "X.Y.Z" { ////} //// -////{| "itemName": "'X2.Y2.Z2'", "kind": "module" |} +////{| "itemName": "\"X2.Y2.Z2\"", "kind": "module" |} ////declare module 'X2.Y2.Z2' { ////} //// diff --git a/tests/cases/fourslash/scriptLexicalStructureMultilineStringIdentifiers.ts b/tests/cases/fourslash/scriptLexicalStructureMultilineStringIdentifiers.ts new file mode 100644 index 00000000000..e0a44ba2685 --- /dev/null +++ b/tests/cases/fourslash/scriptLexicalStructureMultilineStringIdentifiers.ts @@ -0,0 +1,40 @@ + +////{| "itemName": "\"Multiline\\r\\nMadness\"", "kind": "module" |} +////declare module "Multiline\r\nMadness" { +////} +//// +////{| "itemName": "\"MultilineMadness\"", "kind": "module" |} +////declare module "Multiline\ +////Madness" { +////} +////declare module "MultilineMadness" {} +//// +////{| "itemName": "Foo", "kind": "interface" |} +////interface Foo { +//// {| "itemName": "\"a1\\\\\\r\\nb\"", "kind": "property", "parentName": "Foo" |} +//// "a1\\\r\nb"; +//// {| "itemName": "\"a2 b\"", "kind": "method", "parentName": "Foo" |} +//// "a2\ +//// \ +//// b"(): Foo; +////} +//// +////{| "itemName": "Bar", "kind": "class" |} +////class Bar implements Foo { +//// {| "itemName": "\"a1\\\\\\r\\nb\"", "kind": "property", "parentName": "Bar" |} +//// 'a1\\\r\nb': Foo; +//// +//// {| "itemName": "\"a2 b\"", "kind": "method", "parentName": "Bar" |} +//// 'a2\ +//// \ +//// b'(): Foo { +//// return this; +//// } +////} + + +test.markers().forEach((marker) => { + verify.getScriptLexicalStructureListContains(marker.data.itemName, marker.data.kind, marker.fileName, marker.data.parentName); +}); + +verify.getScriptLexicalStructureListCount(8); // interface w/ 2 properties, class w/ 2 properties, 1 merged module, and 1 independent module