From 93526ff36000b5983a65114451e14d257799c086 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 22 Jul 2019 02:29:27 -0700 Subject: [PATCH 01/10] Add some test cases for quoted identifiers and ECMAScript symbols --- .../etc/api-documenter-test.api.json | 139 ++++++++++++++++++ .../etc/api-documenter-test.api.md | 8 + .../api-documenter-test.ecmasmbols.example.md | 13 ++ .../api-documenter-test.ecmasmbols.md | 20 +++ ...est.idocinterface3.[ecmasmbols.example].md | 13 ++ ...nter-test.idocinterface3.[not.a.symbol].md | 13 ++ .../api-documenter-test.idocinterface3.md | 8 + ...ter-test.idocinterface3.redundantquotes.md | 13 ++ .../etc/markdown/api-documenter-test.md | 1 + .../api-documenter-test/idocinterface3.yml | 40 +++++ .../api-documenter-test/src/DocClass1.ts | 26 ++++ 11 files changed, 294 insertions(+) create mode 100644 build-tests/api-documenter-test/etc/markdown/api-documenter-test.ecmasmbols.example.md create mode 100644 build-tests/api-documenter-test/etc/markdown/api-documenter-test.ecmasmbols.md create mode 100644 build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[ecmasmbols.example].md create mode 100644 build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[not.a.symbol].md create mode 100644 build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.redundantquotes.md diff --git a/build-tests/api-documenter-test/etc/api-documenter-test.api.json b/build-tests/api-documenter-test/etc/api-documenter-test.api.json index 86479be2e64..7696533022a 100644 --- a/build-tests/api-documenter-test/etc/api-documenter-test.api.json +++ b/build-tests/api-documenter-test/etc/api-documenter-test.api.json @@ -667,6 +667,54 @@ } ] }, + { + "kind": "Namespace", + "canonicalReference": "api-documenter-test!EcmaSmbols:namespace", + "docComment": "/**\n * A namespace containing an ECMAScript symbol\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare namespace " + }, + { + "kind": "Reference", + "text": "EcmaSmbols" + }, + { + "kind": "Content", + "text": " " + } + ], + "releaseTag": "Public", + "name": "EcmaSmbols", + "members": [ + { + "kind": "Variable", + "canonicalReference": "api-documenter-test!EcmaSmbols.example:var", + "docComment": "/**\n * An ECMAScript symbol\n */\n", + "excerptTokens": [ + { + "kind": "Reference", + "text": "example" + }, + { + "kind": "Content", + "text": ": " + }, + { + "kind": "Content", + "text": "unique symbol" + } + ], + "releaseTag": "Public", + "name": "example", + "variableTypeTokenRange": { + "startIndex": 2, + "endIndex": 3 + } + } + ] + }, { "kind": "TypeAlias", "docComment": "/**\n * A type alias\n *\n * @public\n */\n", @@ -941,6 +989,72 @@ "releaseTag": "Public", "name": "IDocInterface3", "members": [ + { + "kind": "PropertySignature", + "canonicalReference": "api-documenter-test!IDocInterface3#\"\\\"[EcmaSmbols.example]\\\"\":member", + "docComment": "/**\n * ECMAScript symbol\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "[" + }, + { + "kind": "Reference", + "text": "EcmaSmbols" + }, + { + "kind": "Content", + "text": "." + }, + { + "kind": "Reference", + "text": "example" + }, + { + "kind": "Content", + "text": "]: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ";" + } + ], + "releaseTag": "Public", + "name": "[EcmaSmbols.example]", + "propertyTypeTokenRange": { + "startIndex": 5, + "endIndex": 6 + } + }, + { + "kind": "PropertySignature", + "canonicalReference": "api-documenter-test!IDocInterface3#\"\\\"[not.a.symbol]\\\"\":member", + "docComment": "/**\n * An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "\"[not.a.symbol]\": " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ";" + } + ], + "releaseTag": "Public", + "name": "[not.a.symbol]", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, { "kind": "CallSignature", "docComment": "/**\n * Call signature\n *\n * @param x - the parameter\n */\n", @@ -1063,6 +1177,31 @@ } } ] + }, + { + "kind": "PropertySignature", + "canonicalReference": "api-documenter-test!IDocInterface3#redundantQuotes:member", + "docComment": "/**\n * A quoted identifier with redundant quotes.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "\"redundantQuotes\": " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ";" + } + ], + "releaseTag": "Public", + "name": "redundantQuotes", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } } ], "extendsTokenRanges": [] diff --git a/build-tests/api-documenter-test/etc/api-documenter-test.api.md b/build-tests/api-documenter-test/etc/api-documenter-test.api.md index 2b8cc579bb9..94822382972 100644 --- a/build-tests/api-documenter-test/etc/api-documenter-test.api.md +++ b/build-tests/api-documenter-test/etc/api-documenter-test.api.md @@ -38,6 +38,11 @@ export enum DocEnum { Zero = 0 } +// @public +export namespace EcmaSmbols { + const example: unique symbol; +} + // @public export type ExampleTypeAlias = Promise; @@ -61,9 +66,12 @@ export interface IDocInterface2 extends IDocInterface1 { // @public export interface IDocInterface3 { + [EcmaSmbols.example]: string; + "[not.a.symbol]": string; (x: number): number; [x: string]: string; new (): IDocInterface1; + "redundantQuotes": string; } // @public diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.ecmasmbols.example.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.ecmasmbols.example.md new file mode 100644 index 00000000000..bc53b742377 --- /dev/null +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.ecmasmbols.example.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [EcmaSmbols](./api-documenter-test.ecmasmbols.md) > [example](./api-documenter-test.ecmasmbols.example.md) + +## EcmaSmbols.example variable + +An ECMAScript symbol + +Signature: + +```typescript +example: unique symbol +``` diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.ecmasmbols.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.ecmasmbols.md new file mode 100644 index 00000000000..6dc78e8f8eb --- /dev/null +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.ecmasmbols.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [EcmaSmbols](./api-documenter-test.ecmasmbols.md) + +## EcmaSmbols namespace + +A namespace containing an ECMAScript symbol + +Signature: + +```typescript +export declare namespace EcmaSmbols +``` + +## Variables + +| Variable | Description | +| --- | --- | +| [example](./api-documenter-test.ecmasmbols.example.md) | An ECMAScript symbol | + diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[ecmasmbols.example].md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[ecmasmbols.example].md new file mode 100644 index 00000000000..9c232457703 --- /dev/null +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[ecmasmbols.example].md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [IDocInterface3](./api-documenter-test.idocinterface3.md) > [\[EcmaSmbols.example\]](./api-documenter-test.idocinterface3.[ecmasmbols.example].md) + +## IDocInterface3.\[EcmaSmbols.example\] property + +ECMAScript symbol + +Signature: + +```typescript +[EcmaSmbols.example]: string; +``` diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[not.a.symbol].md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[not.a.symbol].md new file mode 100644 index 00000000000..91ea99acb2d --- /dev/null +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[not.a.symbol].md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [IDocInterface3](./api-documenter-test.idocinterface3.md) > [\[not.a.symbol\]](./api-documenter-test.idocinterface3.[not.a.symbol].md) + +## IDocInterface3.\[not.a.symbol\] property + +An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol. + +Signature: + +```typescript +"[not.a.symbol]": string; +``` diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.md index eb7a995ba86..3a5559ae968 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.md @@ -13,6 +13,14 @@ Some less common TypeScript declaration kinds. export interface IDocInterface3 ``` +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [\[EcmaSmbols.example\]](./api-documenter-test.idocinterface3.[ecmasmbols.example].md) | string | ECMAScript symbol | +| [\[not.a.symbol\]](./api-documenter-test.idocinterface3.[not.a.symbol].md) | string | An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol. | +| [redundantQuotes](./api-documenter-test.idocinterface3.redundantquotes.md) | string | A quoted identifier with redundant quotes. | + ## Methods | Method | Description | diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.redundantquotes.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.redundantquotes.md new file mode 100644 index 00000000000..d777ba79b49 --- /dev/null +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.redundantquotes.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [IDocInterface3](./api-documenter-test.idocinterface3.md) > [redundantQuotes](./api-documenter-test.idocinterface3.redundantquotes.md) + +## IDocInterface3.redundantQuotes property + +A quoted identifier with redundant quotes. + +Signature: + +```typescript +"redundantQuotes": string; +``` diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md index 7947ac53ed5..3dd608da7a6 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md @@ -44,6 +44,7 @@ This project tests various documentation generation scenarios and doc comment sy | Namespace | Description | | --- | --- | +| [EcmaSmbols](./api-documenter-test.ecmasmbols.md) | A namespace containing an ECMAScript symbol | | [OuterNamespace](./api-documenter-test.outernamespace.md) | A top-level namespace | ## Variables diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface3.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface3.yml index aa64d929aa3..5af758892d1 100644 --- a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface3.yml +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface3.yml @@ -8,3 +8,43 @@ items: - typeScript type: interface package: api-documenter-test + children: + - 'api-documenter-test.IDocInterface3.[EcmaSmbols.example]' + - 'api-documenter-test.IDocInterface3.[not.a.symbol]' + - api-documenter-test.IDocInterface3.redundantQuotes + - uid: 'api-documenter-test.IDocInterface3.[EcmaSmbols.example]' + summary: ECMAScript symbol + name: '[EcmaSmbols.example]' + fullName: '[EcmaSmbols.example]' + langs: + - typeScript + type: property + syntax: + content: '[EcmaSmbols.example]: string;' + return: + type: + - string + - uid: 'api-documenter-test.IDocInterface3.[not.a.symbol]' + summary: An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol. + name: '[not.a.symbol]' + fullName: '[not.a.symbol]' + langs: + - typeScript + type: property + syntax: + content: '"[not.a.symbol]": string;' + return: + type: + - string + - uid: api-documenter-test.IDocInterface3.redundantQuotes + summary: A quoted identifier with redundant quotes. + name: redundantQuotes + fullName: redundantQuotes + langs: + - typeScript + type: property + syntax: + content: '"redundantQuotes": string;' + return: + type: + - string diff --git a/build-tests/api-documenter-test/src/DocClass1.ts b/build-tests/api-documenter-test/src/DocClass1.ts index 46a9832b48d..d7c5ee23501 100644 --- a/build-tests/api-documenter-test/src/DocClass1.ts +++ b/build-tests/api-documenter-test/src/DocClass1.ts @@ -54,6 +54,17 @@ export interface IDocInterface2 extends IDocInterface1 { deprecatedExample(): void; } +/** + * A namespace containing an ECMAScript symbol + * @public + */ +export namespace EcmaSmbols { + /** + * An ECMAScript symbol + */ + export const example: unique symbol = Symbol('EcmaSmbols.exampleSymbol'); +} + /** * Some less common TypeScript declaration kinds. * @public @@ -76,6 +87,21 @@ export interface IDocInterface3 { * @param x - the parameter */ [x: string]: string; + + /** + * ECMAScript symbol + */ + [EcmaSmbols.example]: string; + + /** + * A quoted identifier with redundant quotes. + */ + "redundantQuotes": string; + + /** + * An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol. + */ + "[not.a.symbol]": string } /** From 5b53abb5715f3c63675effb4e7c07078ac658189 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 22 Jul 2019 12:14:19 -0700 Subject: [PATCH 02/10] Make AstSymbol.localName more rigorous --- .../src/analyzer/AstSymbolTable.ts | 98 +++++++++++++------ .../src/analyzer/StringChecks.ts | 51 ++++++++++ 2 files changed, 120 insertions(+), 29 deletions(-) create mode 100644 apps/api-extractor/src/analyzer/StringChecks.ts diff --git a/apps/api-extractor/src/analyzer/AstSymbolTable.ts b/apps/api-extractor/src/analyzer/AstSymbolTable.ts index 10b93378c32..fca89bc6f83 100644 --- a/apps/api-extractor/src/analyzer/AstSymbolTable.ts +++ b/apps/api-extractor/src/analyzer/AstSymbolTable.ts @@ -13,6 +13,7 @@ import { ExportAnalyzer } from './ExportAnalyzer'; import { AstImport } from './AstImport'; import { MessageRouter } from '../collector/MessageRouter'; import { TypeScriptInternals } from './TypeScriptInternals'; +import { StringChecks } from './StringChecks'; export type AstEntity = AstSymbol | AstImport; @@ -210,6 +211,73 @@ export class AstSymbolTable { return this._entitiesByIdentifierNode.get(identifier); } + /** + * Builds an AstSymbol.localName for a given ts.Symbol. In the current implementation, the localName is + * a TypeScript-like expression that may be a string literal or ECMAScript symbol expression. + * + * ```ts + * class X { + * // localName="identifier" + * public identifier: number = 1; + * // localName="\"identifier\"" + * public "quoted string!": number = 2; + * // localName="[MyNamespace.MySymbol]" + * public [MyNamespace.MySymbol]: number = 3; + * } + * ``` + */ + public static getLocalNameForSymbol(symbol: ts.Symbol): string { + const symbolName: string = symbol.name; + + if (TypeScriptHelpers.isWellKnownSymbolName(symbolName)) { + // TypeScript binds well-known ECMAScript symbols like "Symbol.iterator" as "__@iterator". + // This converts a string like "__@iterator" into the property name "[Symbol.iterator]". + return `[Symbol.${symbolName.slice(3)}]`; + } + + const isUniqueSymbol: boolean = TypeScriptHelpers.isUniqueSymbolName(symbolName); + + // We will try to obtain the name from a declaration; otherwise we'll fall back to the symbol name. + // This handles cases such as "export default class X { }" where the symbol name is "default" + // but the declaration name is "X". + for (const declaration of symbol.declarations || []) { + const declarationName: ts.DeclarationName | undefined = ts.getNameOfDeclaration(declaration); + + if (declarationName && ts.isIdentifier(declarationName)) { + // It's an ordinary identifier, so the symbolName is what we want + break; + } + + // If it is a non-well-known symbol, then return the late bound name + if (isUniqueSymbol && declarationName && ts.isComputedPropertyName(declarationName)) { + const lateBoundName: string | undefined = TypeScriptHelpers.tryGetLateBoundName(declarationName); + if (lateBoundName) { + // Here the string may contain an expression such as "[x.y.z]". Names starting with "[" are always + // expressions. If a string literal contains those characters, the code below will JSON.stringify() it + // to avoid a collision. + return lateBoundName; + } + } + } + + // Otherwise that name may come from a quoted string or pseudonym like `__constructor`. + // If the string is not a safe identifier, then we must add quotes. + // Note that if it was quoted but did not need to be quoted, here we will remove the quotes. + if (!StringChecks.isSafeUnquotedMemberIdentifier(symbolName)) { + // For API Extractor's purposes, a canonical form is more appropriate than trying to reflect whatever + // appeared in the source code. The code is not even guaranteed to be consistent, for example: + // + // class X { + // public "f1"(x: string): void; + // public f1(x: boolean): void; + // public 'f1'(x: string | boolean): void { } + // } + return JSON.stringify(symbolName); + } + + return symbolName; + } + /** * Used by analyze to recursively analyze the entire child tree. */ @@ -425,35 +493,7 @@ export class AstSymbolTable { } } - let localName: string | undefined = options.localName; - - if (localName === undefined) { - // We will try to obtain the name from a declaration; otherwise we'll fall back to the symbol name - // This handles cases such as "export default class X { }" where the symbol name is "default" - // but the declaration name is "X". - localName = followedSymbol.name; - if (TypeScriptHelpers.isWellKnownSymbolName(localName)) { - // TypeScript binds well-known ECMAScript symbols like "Symbol.iterator" as "__@iterator". - // This converts a string like "__@iterator" into the property name "[Symbol.iterator]". - localName = `[Symbol.${localName.slice(3)}]`; - } else { - const isUniqueSymbol: boolean = TypeScriptHelpers.isUniqueSymbolName(localName); - for (const declaration of followedSymbol.declarations || []) { - const declarationName: ts.DeclarationName | undefined = ts.getNameOfDeclaration(declaration); - if (declarationName && ts.isIdentifier(declarationName)) { - localName = declarationName.getText().trim(); - break; - } - if (isUniqueSymbol && declarationName && ts.isComputedPropertyName(declarationName)) { - const lateBoundName: string | undefined = TypeScriptHelpers.tryGetLateBoundName(declarationName); - if (lateBoundName) { - localName = lateBoundName; - break; - } - } - } - } - } + const localName: string | undefined = options.localName || AstSymbolTable.getLocalNameForSymbol(followedSymbol); astSymbol = new AstSymbol({ followedSymbol: followedSymbol, diff --git a/apps/api-extractor/src/analyzer/StringChecks.ts b/apps/api-extractor/src/analyzer/StringChecks.ts new file mode 100644 index 00000000000..aa99b8774d0 --- /dev/null +++ b/apps/api-extractor/src/analyzer/StringChecks.ts @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** + * Helpers for validating various text string formats. + */ +export class StringChecks { + // Note: In addition to letters, numbers, underscores, and dollar signs, modern ECMAScript + // also allows Unicode categories such as letters, combining marks, digits, and connector punctuation. + // These are mostly supported in all environments except IE11, so if someone wants it, we would accept + // a PR to allow them (although the test surface might be somewhat large). + private static readonly _identifierBadCharRegExp: RegExp = /[^a-z0-9_$]/i; + + // Identifiers most not start with a number. + private static readonly _identifierNumberStartRegExp: RegExp = /^[0-9]/; + + /** + * Tests whether the input string is safe to use as an ECMAScript identifier without quotes. + * + * @remarks + * For example: + * + * ```ts + * class X { + * public okay: number = 1; + * public "not okay!": number = 2; + * } + * ``` + * + * A precise check is extremely complicated and highly dependent on the ECMAScript standard version + * and how faithfully the interpreter implements it. To keep things simple, `isValidUnquotedIdentifier()` + * conservatively checks for basic alphanumeric identifiers and returns false otherwise. + * + * Based on `StringChecks.explainIfInvalidUnquotedIdentifier()` from TSDoc. + */ + public static isSafeUnquotedMemberIdentifier(identifier: string): boolean { + if (identifier.length === 0) { + return false; // cannot be empty + } + + if (StringChecks._identifierBadCharRegExp.test(identifier)) { + return false; // cannot contain bad characters + } + + if (StringChecks._identifierNumberStartRegExp.test(identifier)) { + return false; // cannot start with a number + } + + return true; + } +} From 2f80065ef6f51d07b1440294a81394f9e143236d Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 22 Jul 2019 12:19:14 -0700 Subject: [PATCH 03/10] rush change --- .../octogonz-ae-name-escaping_2019-07-22-19-15.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@microsoft/api-extractor/octogonz-ae-name-escaping_2019-07-22-19-15.json diff --git a/common/changes/@microsoft/api-extractor/octogonz-ae-name-escaping_2019-07-22-19-15.json b/common/changes/@microsoft/api-extractor/octogonz-ae-name-escaping_2019-07-22-19-15.json new file mode 100644 index 00000000000..5262a7ede25 --- /dev/null +++ b/common/changes/@microsoft/api-extractor/octogonz-ae-name-escaping_2019-07-22-19-15.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@microsoft/api-extractor", + "comment": "ApiItem.name is now quoted when it contains invalid identifier characters, to avoid conflicts with an ECMAScript symbol expression", + "type": "patch" + } + ], + "packageName": "@microsoft/api-extractor", + "email": "4673363+octogonz@users.noreply.github.com" +} \ No newline at end of file From 4027d3504bb66bd030ddd51fe4c69e95076ed3fc Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 22 Jul 2019 15:55:55 -0700 Subject: [PATCH 04/10] Fix a regression for the defaultExportOfEntryPoint scenario --- .../src/analyzer/AstSymbolTable.ts | 37 ++++++++++--------- .../src/analyzer/TypeScriptInternals.ts | 9 +++++ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/apps/api-extractor/src/analyzer/AstSymbolTable.ts b/apps/api-extractor/src/analyzer/AstSymbolTable.ts index fca89bc6f83..e7051741054 100644 --- a/apps/api-extractor/src/analyzer/AstSymbolTable.ts +++ b/apps/api-extractor/src/analyzer/AstSymbolTable.ts @@ -238,24 +238,27 @@ export class AstSymbolTable { const isUniqueSymbol: boolean = TypeScriptHelpers.isUniqueSymbolName(symbolName); // We will try to obtain the name from a declaration; otherwise we'll fall back to the symbol name. - // This handles cases such as "export default class X { }" where the symbol name is "default" - // but the declaration name is "X". - for (const declaration of symbol.declarations || []) { - const declarationName: ts.DeclarationName | undefined = ts.getNameOfDeclaration(declaration); + let unquotedName: string = symbolName; - if (declarationName && ts.isIdentifier(declarationName)) { - // It's an ordinary identifier, so the symbolName is what we want - break; + for (const declaration of symbol.declarations || []) { + // Handle cases such as "export default class X { }" where the symbol name is "default" + // but the local name is "X". + const localSymbol: ts.Symbol | undefined = TypeScriptInternals.tryGetLocalSymbol(declaration); + if (localSymbol) { + unquotedName = localSymbol.name; } // If it is a non-well-known symbol, then return the late bound name - if (isUniqueSymbol && declarationName && ts.isComputedPropertyName(declarationName)) { - const lateBoundName: string | undefined = TypeScriptHelpers.tryGetLateBoundName(declarationName); - if (lateBoundName) { - // Here the string may contain an expression such as "[x.y.z]". Names starting with "[" are always - // expressions. If a string literal contains those characters, the code below will JSON.stringify() it - // to avoid a collision. - return lateBoundName; + if (isUniqueSymbol) { + const declarationName: ts.DeclarationName | undefined = ts.getNameOfDeclaration(declaration); + if (declarationName && ts.isComputedPropertyName(declarationName)) { + const lateBoundName: string | undefined = TypeScriptHelpers.tryGetLateBoundName(declarationName); + if (lateBoundName) { + // Here the string may contain an expression such as "[x.y.z]". Names starting with "[" are always + // expressions. If a string literal contains those characters, the code below will JSON.stringify() it + // to avoid a collision. + return lateBoundName; + } } } } @@ -263,7 +266,7 @@ export class AstSymbolTable { // Otherwise that name may come from a quoted string or pseudonym like `__constructor`. // If the string is not a safe identifier, then we must add quotes. // Note that if it was quoted but did not need to be quoted, here we will remove the quotes. - if (!StringChecks.isSafeUnquotedMemberIdentifier(symbolName)) { + if (!StringChecks.isSafeUnquotedMemberIdentifier(unquotedName)) { // For API Extractor's purposes, a canonical form is more appropriate than trying to reflect whatever // appeared in the source code. The code is not even guaranteed to be consistent, for example: // @@ -272,10 +275,10 @@ export class AstSymbolTable { // public f1(x: boolean): void; // public 'f1'(x: string | boolean): void { } // } - return JSON.stringify(symbolName); + return JSON.stringify(unquotedName); } - return symbolName; + return unquotedName; } /** diff --git a/apps/api-extractor/src/analyzer/TypeScriptInternals.ts b/apps/api-extractor/src/analyzer/TypeScriptInternals.ts index 1ff7912a1e7..ac184390d21 100644 --- a/apps/api-extractor/src/analyzer/TypeScriptInternals.ts +++ b/apps/api-extractor/src/analyzer/TypeScriptInternals.ts @@ -83,4 +83,13 @@ export class TypeScriptInternals { public static getSymbolParent(symbol: ts.Symbol): ts.Symbol | undefined { return (symbol as any).parent; } + + /** + * In an statement like `export default class X { }`, the `Symbol.name` will be `default` + * whereas the `localSymbol` is `X`. + */ + public static tryGetLocalSymbol(declaration: ts.Declaration): ts.Symbol | undefined { + return (declaration as any).localSymbol; + } + } From 76017371fcc1151fc6a31f694e1098e220a4a0e9 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 22 Jul 2019 16:03:05 -0700 Subject: [PATCH 05/10] Prevent characters such as `"` and `[` from being used in filenames --- .../src/documenters/MarkdownDocumenter.ts | 6 +++--- apps/api-documenter/src/documenters/YamlDocumenter.ts | 6 +++--- apps/api-documenter/src/utils/Utilities.ts | 10 ++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/api-documenter/src/documenters/MarkdownDocumenter.ts b/apps/api-documenter/src/documenters/MarkdownDocumenter.ts index 7b959191e88..08bee3be970 100644 --- a/apps/api-documenter/src/documenters/MarkdownDocumenter.ts +++ b/apps/api-documenter/src/documenters/MarkdownDocumenter.ts @@ -791,7 +791,7 @@ export class MarkdownDocumenter { let baseName: string = ''; for (const hierarchyItem of apiItem.getHierarchy()) { // For overloaded methods, add a suffix such as "MyClass.myMethod_2". - let qualifiedName: string = hierarchyItem.displayName; + let qualifiedName: string = Utilities.getSafeFilenameForName(hierarchyItem.displayName); if (ApiParameterListMixin.isBaseClassOf(hierarchyItem)) { if (hierarchyItem.overloadIndex > 1) { // Subtract one for compatibility with earlier releases of API Documenter. @@ -805,13 +805,13 @@ export class MarkdownDocumenter { case ApiItemKind.EntryPoint: break; case ApiItemKind.Package: - baseName = PackageName.getUnscopedName(hierarchyItem.displayName); + baseName = Utilities.getSafeFilenameForName(PackageName.getUnscopedName(hierarchyItem.displayName)); break; default: baseName += '.' + qualifiedName; } } - return baseName.toLowerCase() + '.md'; + return baseName + '.md'; } private _getLinkFilenameForApiItem(apiItem: ApiItem): string { diff --git a/apps/api-documenter/src/documenters/YamlDocumenter.ts b/apps/api-documenter/src/documenters/YamlDocumenter.ts index 6fd3f21d7f1..137150ce4ad 100644 --- a/apps/api-documenter/src/documenters/YamlDocumenter.ts +++ b/apps/api-documenter/src/documenters/YamlDocumenter.ts @@ -693,7 +693,7 @@ export class YamlDocumenter { case ApiItemKind.EntryPoint: break; case ApiItemKind.Package: - result += PackageName.getUnscopedName(current.displayName); + result += Utilities.getSafeFilenameForName(PackageName.getUnscopedName(current.displayName)); break; default: if (current.parent && current.parent.kind === ApiItemKind.EntryPoint) { @@ -701,11 +701,11 @@ export class YamlDocumenter { } else { result += '.'; } - result += current.displayName; + result += Utilities.getSafeFilenameForName(current.displayName); break; } } - return path.join(this._outputFolder, result.toLowerCase() + '.yml'); + return path.join(this._outputFolder, result + '.yml'); } private _deleteOldOutputFiles(): void { diff --git a/apps/api-documenter/src/utils/Utilities.ts b/apps/api-documenter/src/utils/Utilities.ts index 2c01f5def56..1204aa75a3d 100644 --- a/apps/api-documenter/src/utils/Utilities.ts +++ b/apps/api-documenter/src/utils/Utilities.ts @@ -7,6 +7,7 @@ import { } from '@microsoft/api-extractor-model'; export class Utilities { + private static readonly _badFilenameCharsRegExp: RegExp = /[^\w\-\.]/ig; /** * Generates a concise signature for a function. Example: "getArea(width, height)" */ @@ -16,4 +17,13 @@ export class Utilities { } return apiItem.displayName; } + + /** + * Converts bad filename characters to underscores. + */ + public static getSafeFilenameForName(name: string): string { + // TODO: This can introduce naming collisions. + // We will fix that as part of https://github.com/microsoft/web-build-tools/issues/1308 + return name.replace(Utilities._badFilenameCharsRegExp, '_').toLowerCase(); + } } From 264d7322d6a7fa12fc838b2c9150f340bd55071d Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 22 Jul 2019 16:10:16 -0700 Subject: [PATCH 06/10] rush rebuild --- .../etc/api-documenter-test.api.json | 53 +++++++++---------- .../etc/api-documenter-test.api.md | 2 +- ...menter-test.docbaseclass._constructor_.md} | 2 +- ...nter-test.docbaseclass._constructor__1.md} | 2 +- .../api-documenter-test.docbaseclass.md | 4 +- ...r-test.idocinterface3.__not.a.symbol__.md} | 4 +- ...st.idocinterface3._ecmasmbols.example_.md} | 2 +- ...i-documenter-test.idocinterface3._new_.md} | 2 +- .../api-documenter-test.idocinterface3.md | 6 +-- .../api-documenter-test/idocinterface3.yml | 22 ++++---- 10 files changed, 47 insertions(+), 52 deletions(-) rename build-tests/api-documenter-test/etc/markdown/{api-documenter-test.docbaseclass.(constructor).md => api-documenter-test.docbaseclass._constructor_.md} (81%) rename build-tests/api-documenter-test/etc/markdown/{api-documenter-test.docbaseclass.(constructor)_1.md => api-documenter-test.docbaseclass._constructor__1.md} (85%) rename build-tests/api-documenter-test/etc/markdown/{api-documenter-test.idocinterface3.[not.a.symbol].md => api-documenter-test.idocinterface3.__not.a.symbol__.md} (72%) rename build-tests/api-documenter-test/etc/markdown/{api-documenter-test.idocinterface3.[ecmasmbols.example].md => api-documenter-test.idocinterface3._ecmasmbols.example_.md} (92%) rename build-tests/api-documenter-test/etc/markdown/{api-documenter-test.idocinterface3.(new).md => api-documenter-test.idocinterface3._new_.md} (84%) diff --git a/build-tests/api-documenter-test/etc/api-documenter-test.api.json b/build-tests/api-documenter-test/etc/api-documenter-test.api.json index 7696533022a..86e74c95ece 100644 --- a/build-tests/api-documenter-test/etc/api-documenter-test.api.json +++ b/build-tests/api-documenter-test/etc/api-documenter-test.api.json @@ -669,7 +669,6 @@ }, { "kind": "Namespace", - "canonicalReference": "api-documenter-test!EcmaSmbols:namespace", "docComment": "/**\n * A namespace containing an ECMAScript symbol\n *\n * @public\n */\n", "excerptTokens": [ { @@ -690,7 +689,6 @@ "members": [ { "kind": "Variable", - "canonicalReference": "api-documenter-test!EcmaSmbols.example:var", "docComment": "/**\n * An ECMAScript symbol\n */\n", "excerptTokens": [ { @@ -991,7 +989,30 @@ "members": [ { "kind": "PropertySignature", - "canonicalReference": "api-documenter-test!IDocInterface3#\"\\\"[EcmaSmbols.example]\\\"\":member", + "docComment": "/**\n * An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "\"[not.a.symbol]\": " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ";" + } + ], + "releaseTag": "Public", + "name": "\"[not.a.symbol]\"", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "kind": "PropertySignature", "docComment": "/**\n * ECMAScript symbol\n */\n", "excerptTokens": [ { @@ -1030,31 +1051,6 @@ "endIndex": 6 } }, - { - "kind": "PropertySignature", - "canonicalReference": "api-documenter-test!IDocInterface3#\"\\\"[not.a.symbol]\\\"\":member", - "docComment": "/**\n * An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol.\n */\n", - "excerptTokens": [ - { - "kind": "Content", - "text": "\"[not.a.symbol]\": " - }, - { - "kind": "Content", - "text": "string" - }, - { - "kind": "Content", - "text": ";" - } - ], - "releaseTag": "Public", - "name": "[not.a.symbol]", - "propertyTypeTokenRange": { - "startIndex": 1, - "endIndex": 2 - } - }, { "kind": "CallSignature", "docComment": "/**\n * Call signature\n *\n * @param x - the parameter\n */\n", @@ -1180,7 +1176,6 @@ }, { "kind": "PropertySignature", - "canonicalReference": "api-documenter-test!IDocInterface3#redundantQuotes:member", "docComment": "/**\n * A quoted identifier with redundant quotes.\n */\n", "excerptTokens": [ { diff --git a/build-tests/api-documenter-test/etc/api-documenter-test.api.md b/build-tests/api-documenter-test/etc/api-documenter-test.api.md index 94822382972..d6a0b732545 100644 --- a/build-tests/api-documenter-test/etc/api-documenter-test.api.md +++ b/build-tests/api-documenter-test/etc/api-documenter-test.api.md @@ -66,8 +66,8 @@ export interface IDocInterface2 extends IDocInterface1 { // @public export interface IDocInterface3 { - [EcmaSmbols.example]: string; "[not.a.symbol]": string; + [EcmaSmbols.example]: string; (x: number): number; [x: string]: string; new (): IDocInterface1; diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass.(constructor).md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass._constructor_.md similarity index 81% rename from build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass.(constructor).md rename to build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass._constructor_.md index 3ee89e8decd..9fb39e46aa2 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass.(constructor).md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass._constructor_.md @@ -1,6 +1,6 @@ -[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [DocBaseClass](./api-documenter-test.docbaseclass.md) > [(constructor)](./api-documenter-test.docbaseclass.(constructor).md) +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [DocBaseClass](./api-documenter-test.docbaseclass.md) > [(constructor)](./api-documenter-test.docbaseclass._constructor_.md) ## DocBaseClass.(constructor) diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass.(constructor)_1.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass._constructor__1.md similarity index 85% rename from build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass.(constructor)_1.md rename to build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass._constructor__1.md index 9df618c5f6a..a8380b80c70 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass.(constructor)_1.md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass._constructor__1.md @@ -1,6 +1,6 @@ -[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [DocBaseClass](./api-documenter-test.docbaseclass.md) > [(constructor)](./api-documenter-test.docbaseclass.(constructor)_1.md) +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [DocBaseClass](./api-documenter-test.docbaseclass.md) > [(constructor)](./api-documenter-test.docbaseclass._constructor__1.md) ## DocBaseClass.(constructor) diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass.md index d616a1efdce..792bd688c23 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass.md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docbaseclass.md @@ -17,6 +17,6 @@ export declare class DocBaseClass | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)()](./api-documenter-test.docbaseclass.(constructor).md) | | The simple constructor for DocBaseClass | -| [(constructor)(x)](./api-documenter-test.docbaseclass.(constructor)_1.md) | | The overloaded constructor for DocBaseClass | +| [(constructor)()](./api-documenter-test.docbaseclass._constructor_.md) | | The simple constructor for DocBaseClass | +| [(constructor)(x)](./api-documenter-test.docbaseclass._constructor__1.md) | | The overloaded constructor for DocBaseClass | diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[not.a.symbol].md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.__not.a.symbol__.md similarity index 72% rename from build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[not.a.symbol].md rename to build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.__not.a.symbol__.md index 91ea99acb2d..d776773a478 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[not.a.symbol].md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.__not.a.symbol__.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [IDocInterface3](./api-documenter-test.idocinterface3.md) > [\[not.a.symbol\]](./api-documenter-test.idocinterface3.[not.a.symbol].md) +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [IDocInterface3](./api-documenter-test.idocinterface3.md) > ["\[not.a.symbol\]"](./api-documenter-test.idocinterface3.__not.a.symbol__.md) -## IDocInterface3.\[not.a.symbol\] property +## IDocInterface3."\[not.a.symbol\]" property An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol. diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[ecmasmbols.example].md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3._ecmasmbols.example_.md similarity index 92% rename from build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[ecmasmbols.example].md rename to build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3._ecmasmbols.example_.md index 9c232457703..756b8bf970e 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.[ecmasmbols.example].md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3._ecmasmbols.example_.md @@ -1,6 +1,6 @@ -[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [IDocInterface3](./api-documenter-test.idocinterface3.md) > [\[EcmaSmbols.example\]](./api-documenter-test.idocinterface3.[ecmasmbols.example].md) +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [IDocInterface3](./api-documenter-test.idocinterface3.md) > [\[EcmaSmbols.example\]](./api-documenter-test.idocinterface3._ecmasmbols.example_.md) ## IDocInterface3.\[EcmaSmbols.example\] property diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.(new).md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3._new_.md similarity index 84% rename from build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.(new).md rename to build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3._new_.md index c35f0387eb8..0c0df0a79d0 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.(new).md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3._new_.md @@ -1,6 +1,6 @@ -[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [IDocInterface3](./api-documenter-test.idocinterface3.md) > [(new)](./api-documenter-test.idocinterface3.(new).md) +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [IDocInterface3](./api-documenter-test.idocinterface3.md) > [(new)](./api-documenter-test.idocinterface3._new_.md) ## IDocInterface3.(new) diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.md index 3a5559ae968..28895d133be 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.idocinterface3.md @@ -17,13 +17,13 @@ export interface IDocInterface3 | Property | Type | Description | | --- | --- | --- | -| [\[EcmaSmbols.example\]](./api-documenter-test.idocinterface3.[ecmasmbols.example].md) | string | ECMAScript symbol | -| [\[not.a.symbol\]](./api-documenter-test.idocinterface3.[not.a.symbol].md) | string | An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol. | +| ["\[not.a.symbol\]"](./api-documenter-test.idocinterface3.__not.a.symbol__.md) | string | An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol. | +| [\[EcmaSmbols.example\]](./api-documenter-test.idocinterface3._ecmasmbols.example_.md) | string | ECMAScript symbol | | [redundantQuotes](./api-documenter-test.idocinterface3.redundantquotes.md) | string | A quoted identifier with redundant quotes. | ## Methods | Method | Description | | --- | --- | -| [(new)()](./api-documenter-test.idocinterface3.(new).md) | Construct signature | +| [(new)()](./api-documenter-test.idocinterface3._new_.md) | Construct signature | diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface3.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface3.yml index 5af758892d1..bb06bdee763 100644 --- a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface3.yml +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/idocinterface3.yml @@ -9,30 +9,30 @@ items: type: interface package: api-documenter-test children: + - 'api-documenter-test.IDocInterface3."[not.a.symbol]"' - 'api-documenter-test.IDocInterface3.[EcmaSmbols.example]' - - 'api-documenter-test.IDocInterface3.[not.a.symbol]' - api-documenter-test.IDocInterface3.redundantQuotes - - uid: 'api-documenter-test.IDocInterface3.[EcmaSmbols.example]' - summary: ECMAScript symbol - name: '[EcmaSmbols.example]' - fullName: '[EcmaSmbols.example]' + - uid: 'api-documenter-test.IDocInterface3."[not.a.symbol]"' + summary: An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol. + name: '"[not.a.symbol]"' + fullName: '"[not.a.symbol]"' langs: - typeScript type: property syntax: - content: '[EcmaSmbols.example]: string;' + content: '"[not.a.symbol]": string;' return: type: - string - - uid: 'api-documenter-test.IDocInterface3.[not.a.symbol]' - summary: An identifier that does needs quotes. It misleadingly looks like an ECMAScript symbol. - name: '[not.a.symbol]' - fullName: '[not.a.symbol]' + - uid: 'api-documenter-test.IDocInterface3.[EcmaSmbols.example]' + summary: ECMAScript symbol + name: '[EcmaSmbols.example]' + fullName: '[EcmaSmbols.example]' langs: - typeScript type: property syntax: - content: '"[not.a.symbol]": string;' + content: '[EcmaSmbols.example]: string;' return: type: - string From 286485c8c279198cfaa9d01d44f99da221bdd8ac Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 22 Jul 2019 16:19:21 -0700 Subject: [PATCH 07/10] rush change --- .../octogonz-ae-name-escaping_2019-07-22-23-19.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@microsoft/api-documenter/octogonz-ae-name-escaping_2019-07-22-23-19.json diff --git a/common/changes/@microsoft/api-documenter/octogonz-ae-name-escaping_2019-07-22-23-19.json b/common/changes/@microsoft/api-documenter/octogonz-ae-name-escaping_2019-07-22-23-19.json new file mode 100644 index 00000000000..60cf7ba660b --- /dev/null +++ b/common/changes/@microsoft/api-documenter/octogonz-ae-name-escaping_2019-07-22-23-19.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@microsoft/api-documenter", + "comment": "Fix an issue where generated filenames sometimes mcontained invalid characters", + "type": "patch" + } + ], + "packageName": "@microsoft/api-documenter", + "email": "4673363+octogonz@users.noreply.github.com" +} \ No newline at end of file From db760458e91da1d72301b3e828c686e2d360adac Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 22 Jul 2019 16:55:53 -0700 Subject: [PATCH 08/10] PR feedback --- apps/api-documenter/src/utils/Utilities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api-documenter/src/utils/Utilities.ts b/apps/api-documenter/src/utils/Utilities.ts index 1204aa75a3d..6eb002914d7 100644 --- a/apps/api-documenter/src/utils/Utilities.ts +++ b/apps/api-documenter/src/utils/Utilities.ts @@ -7,7 +7,7 @@ import { } from '@microsoft/api-extractor-model'; export class Utilities { - private static readonly _badFilenameCharsRegExp: RegExp = /[^\w\-\.]/ig; + private static readonly _badFilenameCharsRegExp: RegExp = /[^a-z0-9_\-\.]/ig; /** * Generates a concise signature for a function. Example: "getArea(width, height)" */ From e67b37ac3ed6eaaa6e2baad16665ef0d3a806a02 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 22 Jul 2019 16:57:44 -0700 Subject: [PATCH 09/10] PR feedback: Clarify how well-known symbols get decoded --- .../src/analyzer/AstSymbolTable.ts | 9 +++++---- .../src/analyzer/TypeScriptHelpers.ts | 18 ++++++++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/apps/api-extractor/src/analyzer/AstSymbolTable.ts b/apps/api-extractor/src/analyzer/AstSymbolTable.ts index e7051741054..bd21796d1b8 100644 --- a/apps/api-extractor/src/analyzer/AstSymbolTable.ts +++ b/apps/api-extractor/src/analyzer/AstSymbolTable.ts @@ -229,10 +229,11 @@ export class AstSymbolTable { public static getLocalNameForSymbol(symbol: ts.Symbol): string { const symbolName: string = symbol.name; - if (TypeScriptHelpers.isWellKnownSymbolName(symbolName)) { - // TypeScript binds well-known ECMAScript symbols like "Symbol.iterator" as "__@iterator". - // This converts a string like "__@iterator" into the property name "[Symbol.iterator]". - return `[Symbol.${symbolName.slice(3)}]`; + // TypeScript binds well-known ECMAScript symbols like "[Symbol.iterator]" as "__@iterator". + // Decode it back into "[Symbol.iterator]". + const wellKnownSymbolName: string | undefined = TypeScriptHelpers.tryDecodeWellKnownSymbolName(symbolName); + if (wellKnownSymbolName) { + return wellKnownSymbolName; } const isUniqueSymbol: boolean = TypeScriptHelpers.isUniqueSymbolName(symbolName); diff --git a/apps/api-extractor/src/analyzer/TypeScriptHelpers.ts b/apps/api-extractor/src/analyzer/TypeScriptHelpers.ts index 2f8ff370f98..91a27a7dea8 100644 --- a/apps/api-extractor/src/analyzer/TypeScriptHelpers.ts +++ b/apps/api-extractor/src/analyzer/TypeScriptHelpers.ts @@ -212,13 +212,23 @@ export class TypeScriptHelpers { // Matches TypeScript's encoded names for well-known ECMAScript symbols like // "__@iterator" or "__@toStringTag". - private static readonly _wellKnownSymbolNameRegExp: RegExp = /^__@\w+$/; + private static readonly _wellKnownSymbolNameRegExp: RegExp = /^__@(\w+)$/; /** - * Returns whether the provided name was generated for a built-in ECMAScript symbol. + * Decodes the names that the compiler generates for a built-in ECMAScript symbol. + * + * @remarks + * TypeScript binds well-known ECMAScript symbols like `[Symbol.iterator]` as `__@iterator`. + * If `name` is of this form, then `tryGetWellKnownSymbolName()` converts it back into e.g. `[Symbol.iterator]`. + * If the string does not start with `__@` then `undefined` is returned. */ - public static isWellKnownSymbolName(name: string): boolean { - return TypeScriptHelpers._wellKnownSymbolNameRegExp.test(name); + public static tryDecodeWellKnownSymbolName(name: string): string | undefined { + const match: RegExpExecArray | null = TypeScriptHelpers._wellKnownSymbolNameRegExp.exec(name); + if (match) { + const identifier: string = match[1]; + return `[Symbol.${identifier}]`; + } + return undefined; } // Matches TypeScript's encoded names for late-bound symbols derived from `unique symbol` declarations From 793f1391f10c2a4b435d2743c74e442e4c7f9ce4 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 22 Jul 2019 17:02:15 -0700 Subject: [PATCH 10/10] PR feedback --- apps/api-extractor/src/analyzer/AstSymbolTable.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/api-extractor/src/analyzer/AstSymbolTable.ts b/apps/api-extractor/src/analyzer/AstSymbolTable.ts index bd21796d1b8..8a83c5cfd65 100644 --- a/apps/api-extractor/src/analyzer/AstSymbolTable.ts +++ b/apps/api-extractor/src/analyzer/AstSymbolTable.ts @@ -249,13 +249,24 @@ export class AstSymbolTable { unquotedName = localSymbol.name; } - // If it is a non-well-known symbol, then return the late bound name + // If it is a non-well-known symbol, then return the late-bound name. For example, "X.Y.z" in this example: + // + // namespace X { + // export namespace Y { + // export const z: unique symbol = Symbol("z"); + // } + // } + // + // class C { + // public [X.Y.z](): void { } + // } + // if (isUniqueSymbol) { const declarationName: ts.DeclarationName | undefined = ts.getNameOfDeclaration(declaration); if (declarationName && ts.isComputedPropertyName(declarationName)) { const lateBoundName: string | undefined = TypeScriptHelpers.tryGetLateBoundName(declarationName); if (lateBoundName) { - // Here the string may contain an expression such as "[x.y.z]". Names starting with "[" are always + // Here the string may contain an expression such as "[X.Y.z]". Names starting with "[" are always // expressions. If a string literal contains those characters, the code below will JSON.stringify() it // to avoid a collision. return lateBoundName;