Spaces:
Sleeping
Sleeping
| // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. | |
| // See LICENSE in the project root for license information. | |
| /* eslint-disable @typescript-eslint/no-use-before-define */ | |
| /* eslint-disable @typescript-eslint/naming-convention */ | |
| // NOTE: See DeclarationReference.grammarkdown for information on the underlying grammar. | |
| import { StringChecks } from '../parser/StringChecks'; | |
| /** | |
| * Represents a reference to a declaration. | |
| * @beta | |
| */ | |
| export class DeclarationReference { | |
| constructor(source, navigation, symbol) { | |
| this._source = source; | |
| this._navigation = navigation; | |
| this._symbol = symbol; | |
| } | |
| get source() { | |
| return this._source; | |
| } | |
| get navigation() { | |
| if (!this._source || !this._symbol) { | |
| return undefined; | |
| } | |
| if (this._source === GlobalSource.instance) { | |
| return Navigation.Locals; | |
| } | |
| if (this._navigation === undefined) { | |
| return Navigation.Exports; | |
| } | |
| return this._navigation; | |
| } | |
| get symbol() { | |
| return this._symbol; | |
| } | |
| get isEmpty() { | |
| return this.source === undefined && this.symbol === undefined; | |
| } | |
| static parse(text) { | |
| const parser = new Parser(text); | |
| const reference = parser.parseDeclarationReference(); | |
| if (parser.errors.length) { | |
| throw new SyntaxError(`Invalid DeclarationReference '${text}':\n ${parser.errors.join('\n ')}`); | |
| } | |
| if (!parser.eof) { | |
| throw new SyntaxError(`Invalid DeclarationReference '${text}'`); | |
| } | |
| return reference; | |
| } | |
| static parseComponent(text) { | |
| if (text[0] === '[') { | |
| return ComponentReference.parse(text); | |
| } | |
| else { | |
| return new ComponentString(text, true); | |
| } | |
| } | |
| /** | |
| * Determines whether the provided string is a well-formed symbol navigation component string. | |
| */ | |
| static isWellFormedComponentString(text) { | |
| const scanner = new Scanner(text); | |
| return scanner.scan() === Token.String | |
| ? scanner.scan() === Token.EofToken | |
| : scanner.token() === Token.Text | |
| ? scanner.scan() === Token.EofToken | |
| : scanner.token() === Token.EofToken; | |
| } | |
| /** | |
| * Escapes a string for use as a symbol navigation component. If the string contains any of `!.#~:,"{}()@` or starts | |
| * with `[`, it is enclosed in quotes. | |
| */ | |
| static escapeComponentString(text) { | |
| if (text.length === 0) { | |
| return '""'; | |
| } | |
| const ch = text.charAt(0); | |
| if (ch === '[' || ch === '"' || !this.isWellFormedComponentString(text)) { | |
| return JSON.stringify(text); | |
| } | |
| return text; | |
| } | |
| /** | |
| * Unescapes a string used as a symbol navigation component. | |
| */ | |
| static unescapeComponentString(text) { | |
| if (text.length >= 2 && text.charAt(0) === '"' && text.charAt(text.length - 1) === '"') { | |
| try { | |
| return JSON.parse(text); | |
| } | |
| catch (_a) { | |
| throw new SyntaxError(`Invalid Component '${text}'`); | |
| } | |
| } | |
| if (!this.isWellFormedComponentString(text)) { | |
| throw new SyntaxError(`Invalid Component '${text}'`); | |
| } | |
| return text; | |
| } | |
| /** | |
| * Determines whether the provided string is a well-formed module source string. The string may not | |
| * have a trailing `!` character. | |
| */ | |
| static isWellFormedModuleSourceString(text) { | |
| const scanner = new Scanner(text + '!'); | |
| return (scanner.rescanModuleSource() === Token.ModuleSource && | |
| !scanner.stringIsUnterminated && | |
| scanner.scan() === Token.ExclamationToken && | |
| scanner.scan() === Token.EofToken); | |
| } | |
| /** | |
| * Escapes a string for use as a module source. If the string contains any of `!"` it is enclosed in quotes. | |
| */ | |
| static escapeModuleSourceString(text) { | |
| if (text.length === 0) { | |
| return '""'; | |
| } | |
| const ch = text.charAt(0); | |
| if (ch === '"' || !this.isWellFormedModuleSourceString(text)) { | |
| return JSON.stringify(text); | |
| } | |
| return text; | |
| } | |
| /** | |
| * Unescapes a string used as a module source. The string may not have a trailing `!` character. | |
| */ | |
| static unescapeModuleSourceString(text) { | |
| if (text.length >= 2 && text.charAt(0) === '"' && text.charAt(text.length - 1) === '"') { | |
| try { | |
| return JSON.parse(text); | |
| } | |
| catch (_a) { | |
| throw new SyntaxError(`Invalid Module source '${text}'`); | |
| } | |
| } | |
| if (!this.isWellFormedModuleSourceString(text)) { | |
| throw new SyntaxError(`Invalid Module source '${text}'`); | |
| } | |
| return text; | |
| } | |
| static empty() { | |
| return new DeclarationReference(); | |
| } | |
| static package(packageName, importPath) { | |
| return new DeclarationReference(ModuleSource.fromPackage(packageName, importPath)); | |
| } | |
| static module(path, userEscaped) { | |
| return new DeclarationReference(new ModuleSource(path, userEscaped)); | |
| } | |
| static global() { | |
| return new DeclarationReference(GlobalSource.instance); | |
| } | |
| static from(base) { | |
| return base || this.empty(); | |
| } | |
| withSource(source) { | |
| return this._source === source ? this : new DeclarationReference(source, this._navigation, this._symbol); | |
| } | |
| withNavigation(navigation) { | |
| return this._navigation === navigation | |
| ? this | |
| : new DeclarationReference(this._source, navigation, this._symbol); | |
| } | |
| withSymbol(symbol) { | |
| return this._symbol === symbol ? this : new DeclarationReference(this._source, this._navigation, symbol); | |
| } | |
| withComponentPath(componentPath) { | |
| return this.withSymbol(this.symbol ? this.symbol.withComponentPath(componentPath) : new SymbolReference(componentPath)); | |
| } | |
| withMeaning(meaning) { | |
| if (!this.symbol) { | |
| if (meaning === undefined) { | |
| return this; | |
| } | |
| return this.withSymbol(SymbolReference.empty().withMeaning(meaning)); | |
| } | |
| return this.withSymbol(this.symbol.withMeaning(meaning)); | |
| } | |
| withOverloadIndex(overloadIndex) { | |
| if (!this.symbol) { | |
| if (overloadIndex === undefined) { | |
| return this; | |
| } | |
| return this.withSymbol(SymbolReference.empty().withOverloadIndex(overloadIndex)); | |
| } | |
| return this.withSymbol(this.symbol.withOverloadIndex(overloadIndex)); | |
| } | |
| addNavigationStep(navigation, component) { | |
| if (this.symbol) { | |
| return this.withSymbol(this.symbol.addNavigationStep(navigation, component)); | |
| } | |
| if (navigation === Navigation.Members) { | |
| navigation = Navigation.Exports; | |
| } | |
| const symbol = new SymbolReference(new ComponentRoot(Component.from(component))); | |
| return new DeclarationReference(this.source, navigation, symbol); | |
| } | |
| toString() { | |
| const navigation = this._source instanceof ModuleSource && this._symbol && this.navigation === Navigation.Locals | |
| ? '~' | |
| : ''; | |
| return `${this.source || ''}${navigation}${this.symbol || ''}`; | |
| } | |
| } | |
| /** | |
| * Indicates the symbol table from which to resolve the next symbol component. | |
| * @beta | |
| */ | |
| export var Navigation; | |
| (function (Navigation) { | |
| Navigation["Exports"] = "."; | |
| Navigation["Members"] = "#"; | |
| Navigation["Locals"] = "~"; | |
| })(Navigation || (Navigation = {})); | |
| /** | |
| * Represents a module. | |
| * @beta | |
| */ | |
| export class ModuleSource { | |
| constructor(path, userEscaped = true) { | |
| this.escapedPath = | |
| this instanceof ParsedModuleSource ? path : escapeModuleSourceIfNeeded(path, userEscaped); | |
| } | |
| get path() { | |
| return this._path || (this._path = DeclarationReference.unescapeModuleSourceString(this.escapedPath)); | |
| } | |
| get packageName() { | |
| return this._getOrParsePathComponents().packageName; | |
| } | |
| get scopeName() { | |
| const scopeName = this._getOrParsePathComponents().scopeName; | |
| return scopeName ? '@' + scopeName : ''; | |
| } | |
| get unscopedPackageName() { | |
| return this._getOrParsePathComponents().unscopedPackageName; | |
| } | |
| get importPath() { | |
| return this._getOrParsePathComponents().importPath || ''; | |
| } | |
| static fromScopedPackage(scopeName, unscopedPackageName, importPath) { | |
| let packageName = unscopedPackageName; | |
| if (scopeName) { | |
| if (scopeName.charAt(0) === '@') { | |
| scopeName = scopeName.slice(1); | |
| } | |
| packageName = `@${scopeName}/${unscopedPackageName}`; | |
| } | |
| const parsed = { packageName, scopeName: scopeName || '', unscopedPackageName }; | |
| return this._fromPackageName(parsed, packageName, importPath); | |
| } | |
| static fromPackage(packageName, importPath) { | |
| return this._fromPackageName(parsePackageName(packageName), packageName, importPath); | |
| } | |
| static _fromPackageName(parsed, packageName, importPath) { | |
| if (!parsed) { | |
| throw new Error('Parsed package must be provided.'); | |
| } | |
| const packageNameError = StringChecks.explainIfInvalidPackageName(packageName); | |
| if (packageNameError) { | |
| throw new SyntaxError(`Invalid NPM package name: ${packageNameError}`); | |
| } | |
| let path = packageName; | |
| if (importPath) { | |
| if (invalidImportPathRegExp.test(importPath)) { | |
| throw new SyntaxError(`Invalid import path '${importPath}`); | |
| } | |
| path += '/' + importPath; | |
| parsed.importPath = importPath; | |
| } | |
| const source = new ModuleSource(path); | |
| source._pathComponents = parsed; | |
| return source; | |
| } | |
| toString() { | |
| return `${this.escapedPath}!`; | |
| } | |
| _getOrParsePathComponents() { | |
| if (!this._pathComponents) { | |
| const path = this.path; | |
| const parsed = parsePackageName(path); | |
| if (parsed && !StringChecks.explainIfInvalidPackageName(parsed.packageName)) { | |
| this._pathComponents = parsed; | |
| } | |
| else { | |
| this._pathComponents = { | |
| packageName: '', | |
| scopeName: '', | |
| unscopedPackageName: '', | |
| importPath: path | |
| }; | |
| } | |
| } | |
| return this._pathComponents; | |
| } | |
| } | |
| class ParsedModuleSource extends ModuleSource { | |
| } | |
| // matches the following: | |
| // 'foo' -> ["foo", "foo", undefined, "foo", undefined] | |
| // 'foo/bar' -> ["foo/bar", "foo", undefined, "foo", "bar"] | |
| // '@scope/foo' -> ["@scope/foo", "@scope/foo", "scope", "foo", undefined] | |
| // '@scope/foo/bar' -> ["@scope/foo/bar", "@scope/foo", "scope", "foo", "bar"] | |
| // does not match: | |
| // '/' | |
| // '@/' | |
| // '@scope/' | |
| // capture groups: | |
| // 1. The package name (including scope) | |
| // 2. The scope name (excluding the leading '@') | |
| // 3. The unscoped package name | |
| // 4. The package-relative import path | |
| const packageNameRegExp = /^((?:@([^/]+?)\/)?([^/]+?))(?:\/(.+))?$/; | |
| // no leading './' or '.\' | |
| // no leading '../' or '..\' | |
| // no leading '/' or '\' | |
| // not '.' or '..' | |
| const invalidImportPathRegExp = /^(\.\.?([\\/]|$)|[\\/])/; | |
| // eslint-disable-next-line @rushstack/no-new-null | |
| function parsePackageName(text) { | |
| const match = packageNameRegExp.exec(text); | |
| if (!match) { | |
| return match; | |
| } | |
| const [, packageName = '', scopeName = '', unscopedPackageName = '', importPath] = match; | |
| return { packageName, scopeName, unscopedPackageName, importPath }; | |
| } | |
| /** | |
| * Represents the global scope. | |
| * @beta | |
| */ | |
| export class GlobalSource { | |
| constructor() { } | |
| toString() { | |
| return '!'; | |
| } | |
| } | |
| GlobalSource.instance = new GlobalSource(); | |
| /** | |
| * @beta | |
| */ | |
| // eslint-disable-next-line @typescript-eslint/no-namespace,@typescript-eslint/no-redeclare | |
| export var Component; | |
| (function (Component) { | |
| function from(value) { | |
| if (typeof value === 'string') { | |
| return new ComponentString(value); | |
| } | |
| if (value instanceof DeclarationReference) { | |
| return new ComponentReference(value); | |
| } | |
| return value; | |
| } | |
| Component.from = from; | |
| })(Component || (Component = {})); | |
| /** | |
| * @beta | |
| */ | |
| export class ComponentString { | |
| constructor(text, userEscaped) { | |
| this.text = this instanceof ParsedComponentString ? text : escapeComponentIfNeeded(text, userEscaped); | |
| } | |
| toString() { | |
| return this.text; | |
| } | |
| } | |
| class ParsedComponentString extends ComponentString { | |
| } | |
| /** | |
| * @beta | |
| */ | |
| export class ComponentReference { | |
| constructor(reference) { | |
| this.reference = reference; | |
| } | |
| static parse(text) { | |
| if (text.length > 2 && text.charAt(0) === '[' && text.charAt(text.length - 1) === ']') { | |
| return new ComponentReference(DeclarationReference.parse(text.slice(1, -1))); | |
| } | |
| throw new SyntaxError(`Invalid component reference: '${text}'`); | |
| } | |
| withReference(reference) { | |
| return this.reference === reference ? this : new ComponentReference(reference); | |
| } | |
| toString() { | |
| return `[${this.reference}]`; | |
| } | |
| } | |
| /** | |
| * @beta | |
| */ | |
| export class ComponentPathBase { | |
| constructor(component) { | |
| this.component = component; | |
| } | |
| addNavigationStep(navigation, component) { | |
| // tslint:disable-next-line:no-use-before-declare | |
| return new ComponentNavigation(this, navigation, Component.from(component)); | |
| } | |
| } | |
| /** | |
| * @beta | |
| */ | |
| export class ComponentRoot extends ComponentPathBase { | |
| withComponent(component) { | |
| return this.component === component ? this : new ComponentRoot(Component.from(component)); | |
| } | |
| toString() { | |
| return this.component.toString(); | |
| } | |
| } | |
| /** | |
| * @beta | |
| */ | |
| export class ComponentNavigation extends ComponentPathBase { | |
| constructor(parent, navigation, component) { | |
| super(component); | |
| this.parent = parent; | |
| this.navigation = navigation; | |
| } | |
| withParent(parent) { | |
| return this.parent === parent ? this : new ComponentNavigation(parent, this.navigation, this.component); | |
| } | |
| withNavigation(navigation) { | |
| return this.navigation === navigation | |
| ? this | |
| : new ComponentNavigation(this.parent, navigation, this.component); | |
| } | |
| withComponent(component) { | |
| return this.component === component | |
| ? this | |
| : new ComponentNavigation(this.parent, this.navigation, Component.from(component)); | |
| } | |
| toString() { | |
| return `${this.parent}${formatNavigation(this.navigation)}${this.component}`; | |
| } | |
| } | |
| /** | |
| * @beta | |
| */ | |
| export var Meaning; | |
| (function (Meaning) { | |
| Meaning["Class"] = "class"; | |
| Meaning["Interface"] = "interface"; | |
| Meaning["TypeAlias"] = "type"; | |
| Meaning["Enum"] = "enum"; | |
| Meaning["Namespace"] = "namespace"; | |
| Meaning["Function"] = "function"; | |
| Meaning["Variable"] = "var"; | |
| Meaning["Constructor"] = "constructor"; | |
| Meaning["Member"] = "member"; | |
| Meaning["Event"] = "event"; | |
| Meaning["CallSignature"] = "call"; | |
| Meaning["ConstructSignature"] = "new"; | |
| Meaning["IndexSignature"] = "index"; | |
| Meaning["ComplexType"] = "complex"; // Any complex type | |
| })(Meaning || (Meaning = {})); | |
| /** | |
| * Represents a reference to a TypeScript symbol. | |
| * @beta | |
| */ | |
| export class SymbolReference { | |
| constructor(component, { meaning, overloadIndex } = {}) { | |
| this.componentPath = component; | |
| this.overloadIndex = overloadIndex; | |
| this.meaning = meaning; | |
| } | |
| static empty() { | |
| return new SymbolReference(/*component*/ undefined); | |
| } | |
| withComponentPath(componentPath) { | |
| return this.componentPath === componentPath | |
| ? this | |
| : new SymbolReference(componentPath, { | |
| meaning: this.meaning, | |
| overloadIndex: this.overloadIndex | |
| }); | |
| } | |
| withMeaning(meaning) { | |
| return this.meaning === meaning | |
| ? this | |
| : new SymbolReference(this.componentPath, { | |
| meaning, | |
| overloadIndex: this.overloadIndex | |
| }); | |
| } | |
| withOverloadIndex(overloadIndex) { | |
| return this.overloadIndex === overloadIndex | |
| ? this | |
| : new SymbolReference(this.componentPath, { | |
| meaning: this.meaning, | |
| overloadIndex | |
| }); | |
| } | |
| addNavigationStep(navigation, component) { | |
| if (!this.componentPath) { | |
| throw new Error('Cannot add a navigation step to an empty symbol reference.'); | |
| } | |
| return new SymbolReference(this.componentPath.addNavigationStep(navigation, component)); | |
| } | |
| toString() { | |
| let result = `${this.componentPath || ''}`; | |
| if (this.meaning && this.overloadIndex !== undefined) { | |
| result += `:${this.meaning}(${this.overloadIndex})`; | |
| } | |
| else if (this.meaning) { | |
| result += `:${this.meaning}`; | |
| } | |
| else if (this.overloadIndex !== undefined) { | |
| result += `:${this.overloadIndex}`; | |
| } | |
| return result; | |
| } | |
| } | |
| var Token; | |
| (function (Token) { | |
| Token[Token["None"] = 0] = "None"; | |
| Token[Token["EofToken"] = 1] = "EofToken"; | |
| // Punctuator | |
| Token[Token["OpenBraceToken"] = 2] = "OpenBraceToken"; | |
| Token[Token["CloseBraceToken"] = 3] = "CloseBraceToken"; | |
| Token[Token["OpenParenToken"] = 4] = "OpenParenToken"; | |
| Token[Token["CloseParenToken"] = 5] = "CloseParenToken"; | |
| Token[Token["OpenBracketToken"] = 6] = "OpenBracketToken"; | |
| Token[Token["CloseBracketToken"] = 7] = "CloseBracketToken"; | |
| Token[Token["ExclamationToken"] = 8] = "ExclamationToken"; | |
| Token[Token["DotToken"] = 9] = "DotToken"; | |
| Token[Token["HashToken"] = 10] = "HashToken"; | |
| Token[Token["TildeToken"] = 11] = "TildeToken"; | |
| Token[Token["ColonToken"] = 12] = "ColonToken"; | |
| Token[Token["CommaToken"] = 13] = "CommaToken"; | |
| Token[Token["AtToken"] = 14] = "AtToken"; | |
| Token[Token["DecimalDigits"] = 15] = "DecimalDigits"; | |
| Token[Token["String"] = 16] = "String"; | |
| Token[Token["Text"] = 17] = "Text"; | |
| // eslint-disable-next-line @typescript-eslint/no-shadow | |
| Token[Token["ModuleSource"] = 18] = "ModuleSource"; | |
| // Keywords | |
| Token[Token["ClassKeyword"] = 19] = "ClassKeyword"; | |
| Token[Token["InterfaceKeyword"] = 20] = "InterfaceKeyword"; | |
| Token[Token["TypeKeyword"] = 21] = "TypeKeyword"; | |
| Token[Token["EnumKeyword"] = 22] = "EnumKeyword"; | |
| Token[Token["NamespaceKeyword"] = 23] = "NamespaceKeyword"; | |
| Token[Token["FunctionKeyword"] = 24] = "FunctionKeyword"; | |
| Token[Token["VarKeyword"] = 25] = "VarKeyword"; | |
| Token[Token["ConstructorKeyword"] = 26] = "ConstructorKeyword"; | |
| Token[Token["MemberKeyword"] = 27] = "MemberKeyword"; | |
| Token[Token["EventKeyword"] = 28] = "EventKeyword"; | |
| Token[Token["CallKeyword"] = 29] = "CallKeyword"; | |
| Token[Token["NewKeyword"] = 30] = "NewKeyword"; | |
| Token[Token["IndexKeyword"] = 31] = "IndexKeyword"; | |
| Token[Token["ComplexKeyword"] = 32] = "ComplexKeyword"; // 'complex' | |
| })(Token || (Token = {})); | |
| function tokenToString(token) { | |
| switch (token) { | |
| case Token.OpenBraceToken: | |
| return '{'; | |
| case Token.CloseBraceToken: | |
| return '}'; | |
| case Token.OpenParenToken: | |
| return '('; | |
| case Token.CloseParenToken: | |
| return ')'; | |
| case Token.OpenBracketToken: | |
| return '['; | |
| case Token.CloseBracketToken: | |
| return ']'; | |
| case Token.ExclamationToken: | |
| return '!'; | |
| case Token.DotToken: | |
| return '.'; | |
| case Token.HashToken: | |
| return '#'; | |
| case Token.TildeToken: | |
| return '~'; | |
| case Token.ColonToken: | |
| return ':'; | |
| case Token.CommaToken: | |
| return ','; | |
| case Token.AtToken: | |
| return '@'; | |
| case Token.ClassKeyword: | |
| return 'class'; | |
| case Token.InterfaceKeyword: | |
| return 'interface'; | |
| case Token.TypeKeyword: | |
| return 'type'; | |
| case Token.EnumKeyword: | |
| return 'enum'; | |
| case Token.NamespaceKeyword: | |
| return 'namespace'; | |
| case Token.FunctionKeyword: | |
| return 'function'; | |
| case Token.VarKeyword: | |
| return 'var'; | |
| case Token.ConstructorKeyword: | |
| return 'constructor'; | |
| case Token.MemberKeyword: | |
| return 'member'; | |
| case Token.EventKeyword: | |
| return 'event'; | |
| case Token.CallKeyword: | |
| return 'call'; | |
| case Token.NewKeyword: | |
| return 'new'; | |
| case Token.IndexKeyword: | |
| return 'index'; | |
| case Token.ComplexKeyword: | |
| return 'complex'; | |
| case Token.None: | |
| return '<none>'; | |
| case Token.EofToken: | |
| return '<eof>'; | |
| case Token.DecimalDigits: | |
| return '<decimal digits>'; | |
| case Token.String: | |
| return '<string>'; | |
| case Token.Text: | |
| return '<text>'; | |
| case Token.ModuleSource: | |
| return '<module source>'; | |
| } | |
| } | |
| class Scanner { | |
| constructor(text) { | |
| this._pos = 0; | |
| this._tokenPos = 0; | |
| this._stringIsUnterminated = false; | |
| this._token = Token.None; | |
| this._text = text; | |
| } | |
| get stringIsUnterminated() { | |
| return this._stringIsUnterminated; | |
| } | |
| get text() { | |
| return this._text; | |
| } | |
| get tokenText() { | |
| return this._text.slice(this._tokenPos, this._pos); | |
| } | |
| get eof() { | |
| return this._pos >= this._text.length; | |
| } | |
| token() { | |
| return this._token; | |
| } | |
| speculate(cb) { | |
| const tokenPos = this._tokenPos; | |
| const pos = this._pos; | |
| const text = this._text; | |
| const token = this._token; | |
| const stringIsUnterminated = this._stringIsUnterminated; | |
| let accepted = false; | |
| try { | |
| const accept = () => { | |
| accepted = true; | |
| }; | |
| return cb(accept); | |
| } | |
| finally { | |
| if (!accepted) { | |
| this._tokenPos = tokenPos; | |
| this._pos = pos; | |
| this._text = text; | |
| this._token = token; | |
| this._stringIsUnterminated = stringIsUnterminated; | |
| } | |
| } | |
| } | |
| scan() { | |
| if (!this.eof) { | |
| this._tokenPos = this._pos; | |
| this._stringIsUnterminated = false; | |
| while (!this.eof) { | |
| const ch = this._text.charAt(this._pos++); | |
| switch (ch) { | |
| case '{': | |
| return (this._token = Token.OpenBraceToken); | |
| case '}': | |
| return (this._token = Token.CloseBraceToken); | |
| case '(': | |
| return (this._token = Token.OpenParenToken); | |
| case ')': | |
| return (this._token = Token.CloseParenToken); | |
| case '[': | |
| return (this._token = Token.OpenBracketToken); | |
| case ']': | |
| return (this._token = Token.CloseBracketToken); | |
| case '!': | |
| return (this._token = Token.ExclamationToken); | |
| case '.': | |
| return (this._token = Token.DotToken); | |
| case '#': | |
| return (this._token = Token.HashToken); | |
| case '~': | |
| return (this._token = Token.TildeToken); | |
| case ':': | |
| return (this._token = Token.ColonToken); | |
| case ',': | |
| return (this._token = Token.CommaToken); | |
| case '@': | |
| return (this._token = Token.AtToken); | |
| case '"': | |
| this.scanString(); | |
| return (this._token = Token.String); | |
| default: | |
| this.scanText(); | |
| return (this._token = Token.Text); | |
| } | |
| } | |
| } | |
| return (this._token = Token.EofToken); | |
| } | |
| rescanModuleSource() { | |
| switch (this._token) { | |
| case Token.ModuleSource: | |
| case Token.ExclamationToken: | |
| case Token.EofToken: | |
| return this._token; | |
| } | |
| return this.speculate((accept) => { | |
| if (!this.eof) { | |
| this._pos = this._tokenPos; | |
| this._stringIsUnterminated = false; | |
| let scanned = 'none'; | |
| while (!this.eof) { | |
| const ch = this._text[this._pos]; | |
| if (ch === '!') { | |
| if (scanned === 'none') { | |
| return this._token; | |
| } | |
| accept(); | |
| return (this._token = Token.ModuleSource); | |
| } | |
| this._pos++; | |
| if (ch === '"') { | |
| if (scanned === 'other') { | |
| // strings not allowed after scanning any other characters | |
| return this._token; | |
| } | |
| scanned = 'string'; | |
| this.scanString(); | |
| } | |
| else { | |
| if (scanned === 'string') { | |
| // no other tokens allowed after string | |
| return this._token; | |
| } | |
| scanned = 'other'; | |
| if (!isPunctuator(ch)) { | |
| this.scanText(); | |
| } | |
| } | |
| } | |
| } | |
| return this._token; | |
| }); | |
| } | |
| rescanMeaning() { | |
| if (this._token === Token.Text) { | |
| const tokenText = this.tokenText; | |
| switch (tokenText) { | |
| case 'class': | |
| return (this._token = Token.ClassKeyword); | |
| case 'interface': | |
| return (this._token = Token.InterfaceKeyword); | |
| case 'type': | |
| return (this._token = Token.TypeKeyword); | |
| case 'enum': | |
| return (this._token = Token.EnumKeyword); | |
| case 'namespace': | |
| return (this._token = Token.NamespaceKeyword); | |
| case 'function': | |
| return (this._token = Token.FunctionKeyword); | |
| case 'var': | |
| return (this._token = Token.VarKeyword); | |
| case 'constructor': | |
| return (this._token = Token.ConstructorKeyword); | |
| case 'member': | |
| return (this._token = Token.MemberKeyword); | |
| case 'event': | |
| return (this._token = Token.EventKeyword); | |
| case 'call': | |
| return (this._token = Token.CallKeyword); | |
| case 'new': | |
| return (this._token = Token.NewKeyword); | |
| case 'index': | |
| return (this._token = Token.IndexKeyword); | |
| case 'complex': | |
| return (this._token = Token.ComplexKeyword); | |
| } | |
| } | |
| return this._token; | |
| } | |
| rescanDecimalDigits() { | |
| if (this._token === Token.Text) { | |
| const tokenText = this.tokenText; | |
| if (/^\d+$/.test(tokenText)) { | |
| return (this._token = Token.DecimalDigits); | |
| } | |
| } | |
| return this._token; | |
| } | |
| scanString() { | |
| while (!this.eof) { | |
| const ch = this._text.charAt(this._pos++); | |
| switch (ch) { | |
| case '"': | |
| return; | |
| case '\\': | |
| this.scanEscapeSequence(); | |
| break; | |
| default: | |
| if (isLineTerminator(ch)) { | |
| this._stringIsUnterminated = true; | |
| return; | |
| } | |
| } | |
| } | |
| this._stringIsUnterminated = true; | |
| } | |
| scanEscapeSequence() { | |
| if (this.eof) { | |
| this._stringIsUnterminated = true; | |
| return; | |
| } | |
| const ch = this._text.charAt(this._pos); | |
| // EscapeSequence:: CharacterEscapeSequence | |
| if (isCharacterEscapeSequence(ch)) { | |
| this._pos++; | |
| return; | |
| } | |
| // EscapeSequence:: `0` [lookahead != DecimalDigit] | |
| if (ch === '0' && | |
| (this._pos + 1 === this._text.length || !isDecimalDigit(this._text.charAt(this._pos + 1)))) { | |
| this._pos++; | |
| return; | |
| } | |
| // EscapeSequence:: HexEscapeSequence | |
| if (ch === 'x' && | |
| this._pos + 3 <= this._text.length && | |
| isHexDigit(this._text.charAt(this._pos + 1)) && | |
| isHexDigit(this._text.charAt(this._pos + 2))) { | |
| this._pos += 3; | |
| return; | |
| } | |
| // EscapeSequence:: UnicodeEscapeSequence | |
| // UnicodeEscapeSequence:: `u` Hex4Digits | |
| if (ch === 'u' && | |
| this._pos + 5 <= this._text.length && | |
| isHexDigit(this._text.charAt(this._pos + 1)) && | |
| isHexDigit(this._text.charAt(this._pos + 2)) && | |
| isHexDigit(this._text.charAt(this._pos + 3)) && | |
| isHexDigit(this._text.charAt(this._pos + 4))) { | |
| this._pos += 5; | |
| return; | |
| } | |
| // EscapeSequence:: UnicodeEscapeSequence | |
| // UnicodeEscapeSequence:: `u` `{` CodePoint `}` | |
| if (ch === 'u' && this._pos + 4 <= this._text.length && this._text.charAt(this._pos + 1) === '{') { | |
| let hexDigits = this._text.charAt(this._pos + 2); | |
| if (isHexDigit(hexDigits)) { | |
| for (let i = this._pos + 3; i < this._text.length; i++) { | |
| const ch2 = this._text.charAt(i); | |
| if (ch2 === '}') { | |
| const mv = parseInt(hexDigits, 16); | |
| if (mv <= 0x10ffff) { | |
| this._pos = i + 1; | |
| return; | |
| } | |
| break; | |
| } | |
| if (!isHexDigit(ch2)) { | |
| hexDigits += ch2; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| this._stringIsUnterminated = true; | |
| } | |
| scanText() { | |
| while (this._pos < this._text.length) { | |
| const ch = this._text.charAt(this._pos); | |
| if (isPunctuator(ch) || ch === '"') { | |
| return; | |
| } | |
| this._pos++; | |
| } | |
| } | |
| } | |
| class Parser { | |
| constructor(text) { | |
| this._errors = []; | |
| this._scanner = new Scanner(text); | |
| this._scanner.scan(); | |
| } | |
| get eof() { | |
| return this.token() === Token.EofToken; | |
| } | |
| get errors() { | |
| return this._errors; | |
| } | |
| parseDeclarationReference() { | |
| let source; | |
| let navigation; | |
| let symbol; | |
| if (this.optionalToken(Token.ExclamationToken)) { | |
| // Reference to global symbol | |
| source = GlobalSource.instance; | |
| } | |
| else if (this._scanner.rescanModuleSource() === Token.ModuleSource) { | |
| source = this.parseModuleSource(); | |
| // Check for optional `~` navigation token. | |
| if (this.optionalToken(Token.TildeToken)) { | |
| navigation = Navigation.Locals; | |
| } | |
| } | |
| if (this.isStartOfComponent()) { | |
| symbol = this.parseSymbol(); | |
| } | |
| else if (this.token() === Token.ColonToken) { | |
| symbol = this.parseSymbolRest(new ComponentRoot(new ComponentString('', /*userEscaped*/ true))); | |
| } | |
| return new DeclarationReference(source, navigation, symbol); | |
| } | |
| parseModuleSourceString() { | |
| this._scanner.rescanModuleSource(); | |
| return this.parseTokenString(Token.ModuleSource, 'Module source'); | |
| } | |
| parseComponentString() { | |
| switch (this._scanner.token()) { | |
| case Token.String: | |
| return this.parseString(); | |
| default: | |
| return this.parseComponentCharacters(); | |
| } | |
| } | |
| token() { | |
| return this._scanner.token(); | |
| } | |
| parseModuleSource() { | |
| const source = this.parseModuleSourceString(); | |
| this.expectToken(Token.ExclamationToken); | |
| return new ParsedModuleSource(source, /*userEscaped*/ true); | |
| } | |
| parseSymbol() { | |
| const component = this.parseComponentRest(this.parseRootComponent()); | |
| return this.parseSymbolRest(component); | |
| } | |
| parseSymbolRest(component) { | |
| let meaning; | |
| let overloadIndex; | |
| if (this.optionalToken(Token.ColonToken)) { | |
| meaning = this.tryParseMeaning(); | |
| overloadIndex = this.tryParseOverloadIndex(!!meaning); | |
| } | |
| return new SymbolReference(component, { meaning, overloadIndex }); | |
| } | |
| parseRootComponent() { | |
| if (!this.isStartOfComponent()) { | |
| return this.fail('Component expected', new ComponentRoot(new ComponentString('', /*userEscaped*/ true))); | |
| } | |
| const component = this.parseComponent(); | |
| return new ComponentRoot(component); | |
| } | |
| parseComponentRest(component) { | |
| for (;;) { | |
| switch (this.token()) { | |
| case Token.DotToken: | |
| case Token.HashToken: | |
| case Token.TildeToken: | |
| const navigation = this.parseNavigation(); | |
| const right = this.parseComponent(); | |
| component = new ComponentNavigation(component, navigation, right); | |
| break; | |
| default: | |
| return component; | |
| } | |
| } | |
| } | |
| parseNavigation() { | |
| switch (this._scanner.token()) { | |
| case Token.DotToken: { | |
| this._scanner.scan(); | |
| return Navigation.Exports; | |
| } | |
| case Token.HashToken: { | |
| this._scanner.scan(); | |
| return Navigation.Members; | |
| } | |
| case Token.TildeToken: { | |
| this._scanner.scan(); | |
| return Navigation.Locals; | |
| } | |
| default: { | |
| return this.fail("Expected '.', '#', or '~'", Navigation.Exports); | |
| } | |
| } | |
| } | |
| tryParseMeaning() { | |
| switch (this._scanner.rescanMeaning()) { | |
| case Token.ClassKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.Class; | |
| } | |
| case Token.InterfaceKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.Interface; | |
| } | |
| case Token.TypeKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.TypeAlias; | |
| } | |
| case Token.EnumKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.Enum; | |
| } | |
| case Token.NamespaceKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.Namespace; | |
| } | |
| case Token.FunctionKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.Function; | |
| } | |
| case Token.VarKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.Variable; | |
| } | |
| case Token.ConstructorKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.Constructor; | |
| } | |
| case Token.MemberKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.Member; | |
| } | |
| case Token.EventKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.Event; | |
| } | |
| case Token.CallKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.CallSignature; | |
| } | |
| case Token.NewKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.ConstructSignature; | |
| } | |
| case Token.IndexKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.IndexSignature; | |
| } | |
| case Token.ComplexKeyword: { | |
| this._scanner.scan(); | |
| return Meaning.ComplexType; | |
| } | |
| } | |
| } | |
| tryParseOverloadIndex(hasMeaning) { | |
| if (this.optionalToken(Token.OpenParenToken)) { | |
| const overloadIndex = this.parseDecimalDigits(); | |
| this.expectToken(Token.CloseParenToken); | |
| return overloadIndex; | |
| } | |
| else if (!hasMeaning) { | |
| return this.parseDecimalDigits(); | |
| } | |
| return undefined; | |
| } | |
| parseDecimalDigits() { | |
| switch (this._scanner.rescanDecimalDigits()) { | |
| case Token.DecimalDigits: | |
| const value = +this._scanner.tokenText; | |
| this._scanner.scan(); | |
| return value; | |
| default: | |
| return this.fail('Decimal digit expected', 0); | |
| } | |
| } | |
| isStartOfComponent() { | |
| switch (this.token()) { | |
| case Token.Text: | |
| case Token.String: | |
| case Token.OpenBracketToken: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| parseComponentCharacters() { | |
| let text = ''; | |
| for (;;) { | |
| switch (this._scanner.token()) { | |
| case Token.Text: | |
| text += this.parseText(); | |
| break; | |
| default: | |
| return text; | |
| } | |
| } | |
| } | |
| parseTokenString(token, tokenString) { | |
| if (this._scanner.token() === token) { | |
| const text = this._scanner.tokenText; | |
| const stringIsUnterminated = this._scanner.stringIsUnterminated; | |
| this._scanner.scan(); | |
| if (stringIsUnterminated) { | |
| return this.fail(`${tokenString || tokenToString(token)} is unterminated`, text); | |
| } | |
| return text; | |
| } | |
| return this.fail(`${tokenString || tokenToString(token)} expected`, ''); | |
| } | |
| parseText() { | |
| return this.parseTokenString(Token.Text, 'Text'); | |
| } | |
| parseString() { | |
| return this.parseTokenString(Token.String, 'String'); | |
| } | |
| parseComponent() { | |
| switch (this._scanner.token()) { | |
| case Token.OpenBracketToken: | |
| return this.parseBracketedComponent(); | |
| default: | |
| return new ParsedComponentString(this.parseComponentString(), /*userEscaped*/ true); | |
| } | |
| } | |
| parseBracketedComponent() { | |
| this.expectToken(Token.OpenBracketToken); | |
| const reference = this.parseDeclarationReference(); | |
| this.expectToken(Token.CloseBracketToken); | |
| return new ComponentReference(reference); | |
| } | |
| optionalToken(token) { | |
| if (this._scanner.token() === token) { | |
| this._scanner.scan(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| expectToken(token, message) { | |
| if (this._scanner.token() !== token) { | |
| const expected = tokenToString(token); | |
| const actual = tokenToString(this._scanner.token()); | |
| return this.fail(message || `Expected token '${expected}', received '${actual}' instead.`, undefined); | |
| } | |
| this._scanner.scan(); | |
| } | |
| fail(message, fallback) { | |
| this._errors.push(message); | |
| return fallback; | |
| } | |
| } | |
| function formatNavigation(navigation) { | |
| switch (navigation) { | |
| case Navigation.Exports: | |
| return '.'; | |
| case Navigation.Members: | |
| return '#'; | |
| case Navigation.Locals: | |
| return '~'; | |
| default: | |
| return ''; | |
| } | |
| } | |
| function isCharacterEscapeSequence(ch) { | |
| return isSingleEscapeCharacter(ch) || isNonEscapeCharacter(ch); | |
| } | |
| function isSingleEscapeCharacter(ch) { | |
| switch (ch) { | |
| case "'": | |
| case '"': | |
| case '\\': | |
| case 'b': | |
| case 'f': | |
| case 'n': | |
| case 'r': | |
| case 't': | |
| case 'v': | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| function isNonEscapeCharacter(ch) { | |
| return !isEscapeCharacter(ch) && !isLineTerminator(ch); | |
| } | |
| function isEscapeCharacter(ch) { | |
| switch (ch) { | |
| case 'x': | |
| case 'u': | |
| return true; | |
| default: | |
| return isSingleEscapeCharacter(ch) || isDecimalDigit(ch); | |
| } | |
| } | |
| function isLineTerminator(ch) { | |
| switch (ch) { | |
| case '\r': | |
| case '\n': | |
| // TODO: <LS>, <PS> | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| function isDecimalDigit(ch) { | |
| switch (ch) { | |
| case '0': | |
| case '1': | |
| case '2': | |
| case '3': | |
| case '4': | |
| case '5': | |
| case '6': | |
| case '7': | |
| case '8': | |
| case '9': | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| function isHexDigit(ch) { | |
| switch (ch) { | |
| case 'a': | |
| case 'b': | |
| case 'c': | |
| case 'd': | |
| case 'e': | |
| case 'f': | |
| case 'A': | |
| case 'B': | |
| case 'C': | |
| case 'D': | |
| case 'E': | |
| case 'F': | |
| return true; | |
| default: | |
| return isDecimalDigit(ch); | |
| } | |
| } | |
| function isPunctuator(ch) { | |
| switch (ch) { | |
| case '{': | |
| case '}': | |
| case '(': | |
| case ')': | |
| case '[': | |
| case ']': | |
| case '!': | |
| case '.': | |
| case '#': | |
| case '~': | |
| case ':': | |
| case ',': | |
| case '@': | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| function escapeComponentIfNeeded(text, userEscaped) { | |
| if (userEscaped) { | |
| if (!DeclarationReference.isWellFormedComponentString(text)) { | |
| throw new SyntaxError(`Invalid Component '${text}'`); | |
| } | |
| return text; | |
| } | |
| return DeclarationReference.escapeComponentString(text); | |
| } | |
| function escapeModuleSourceIfNeeded(text, userEscaped) { | |
| if (userEscaped) { | |
| if (!DeclarationReference.isWellFormedModuleSourceString(text)) { | |
| throw new SyntaxError(`Invalid Module source '${text}'`); | |
| } | |
| return text; | |
| } | |
| return DeclarationReference.escapeModuleSourceString(text); | |
| } | |
| //# sourceMappingURL=DeclarationReference.js.map |