UNPKG

@microsoft/api-extractor

Version:

Validate, document, and review the exported API for a TypeScript library

187 lines (185 loc) 8.79 kB
"use strict"; // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. Object.defineProperty(exports, "__esModule", { value: true }); /* tslint:disable:no-bitwise */ const ts = require("typescript"); const ReleaseTag_1 = require("../aedoc/ReleaseTag"); const Markup_1 = require("../markup/Markup"); const AstMethod_1 = require("./AstMethod"); const AstProperty_1 = require("./AstProperty"); const AstItem_1 = require("./AstItem"); const AstItemContainer_1 = require("./AstItemContainer"); const TypeScriptHelpers_1 = require("../TypeScriptHelpers"); const PrettyPrinter_1 = require("../PrettyPrinter"); /** * This class is part of the AstItem abstract syntax tree. It represents a class, * interface, or type literal expression. */ class AstStructuredType extends AstItemContainer_1.default { constructor(options) { super(options); this._processedMemberNames = new Set(); this._setterNames = new Set(); this._classLikeDeclaration = options.declaration; this.type = this.typeChecker.getDeclaredTypeOfSymbol(this.declarationSymbol); if (this.declarationSymbol.flags & ts.SymbolFlags.Interface) { this.kind = AstItem_1.AstItemKind.Interface; } else if (this.declarationSymbol.flags & ts.SymbolFlags.TypeLiteral) { this.kind = AstItem_1.AstItemKind.TypeLiteral; } else { this.kind = AstItem_1.AstItemKind.Class; } for (const memberDeclaration of this._classLikeDeclaration.members) { const memberSymbol = TypeScriptHelpers_1.default.tryGetSymbolForDeclaration(memberDeclaration); if (memberSymbol) { this._processMember(memberSymbol, memberDeclaration); } else { // If someone put an extra semicolon after their function, we don't care about that if (memberDeclaration.kind !== ts.SyntaxKind.SemicolonClassElement) { // If there is some other non-semantic junk, add a warning so we can investigate it this.reportWarning(PrettyPrinter_1.default.formatFileAndLineNumber(memberDeclaration) + `: No semantic information for "${memberDeclaration.getText()}"`); } } } // If there is a getter and no setter, mark it as readonly. for (const member of this.getSortedMemberItems()) { const memberSymbol = TypeScriptHelpers_1.default.tryGetSymbolForDeclaration(member.getDeclaration()); if (memberSymbol && (memberSymbol.flags === ts.SymbolFlags.GetAccessor)) { if (!this._setterNames.has(member.name)) { member.isReadOnly = true; } } } // Check for heritage clauses (implements and extends) if (this._classLikeDeclaration.heritageClauses) { for (const heritage of this._classLikeDeclaration.heritageClauses) { const typeText = heritage.types && heritage.types.length && heritage.types[0].expression ? heritage.types[0].expression.getText() : undefined; if (heritage.token === ts.SyntaxKind.ExtendsKeyword) { this.extends = typeText; } else if (heritage.token === ts.SyntaxKind.ImplementsKeyword) { this.implements = typeText; } } } // Check for type parameters if (this._classLikeDeclaration.typeParameters && this._classLikeDeclaration.typeParameters.length) { if (!this.typeParameters) { this.typeParameters = []; } for (const param of this._classLikeDeclaration.typeParameters) { this.typeParameters.push(param.getText()); } } // Throw errors for setters that don't have a corresponding getter this._setterNames.forEach((setterName) => { if (!this.getMemberItem(setterName)) { // Normally we treat API design changes as warnings rather than errors. However, // a missing getter is bizarre enough that it's reasonable to assume it's a mistake, // not a conscious design choice. this.reportError(`The "${setterName}" property has a setter, but no a getter`); } }); } /** * @virtual */ visitTypeReferencesForAstItem() { super.visitTypeReferencesForAstItem(); // Collect type references from the base classes if (this._classLikeDeclaration && this._classLikeDeclaration.heritageClauses) { for (const clause of this._classLikeDeclaration.heritageClauses) { this.visitTypeReferencesForNode(clause); } } } /** * Returns a line of text such as "class MyClass extends MyBaseClass", excluding the * curly braces and body. The name "MyClass" will be the public name seen by external * callers, not the declared name of the class; @see AstItem.name documentation for details. */ getDeclarationLine() { let result = ''; if (this.kind !== AstItem_1.AstItemKind.TypeLiteral) { result += (this.declarationSymbol.flags & ts.SymbolFlags.Interface) ? 'interface ' : 'class '; result += this.name; if (this._classLikeDeclaration.typeParameters) { result += '<'; result += this._classLikeDeclaration.typeParameters .map((param) => param.getText()) .join(', '); result += '>'; } if (this._classLikeDeclaration.heritageClauses) { result += ' '; result += this._classLikeDeclaration.heritageClauses .map((clause) => clause.getText()) .join(', '); } } return result; } onCompleteInitialization() { super.onCompleteInitialization(); // Is the constructor internal? for (const member of this.getSortedMemberItems()) { if (member.kind === AstItem_1.AstItemKind.Constructor) { if (member.documentation.releaseTag === ReleaseTag_1.ReleaseTag.Internal) { // Add a boilerplate notice for classes with internal constructors this.documentation.remarks.unshift(...Markup_1.Markup.createTextElements(`The constructor for this class is marked as internal. Third-party code` + ` should not call the constructor directly or create subclasses that extend the ${this.name} class.`), Markup_1.Markup.PARAGRAPH); } } } } _processMember(memberSymbol, memberDeclaration) { if (memberDeclaration.modifiers) { for (let i = 0; i < memberDeclaration.modifiers.length; i++) { const modifier = memberDeclaration.modifiers[i]; if (modifier.kind === ts.SyntaxKind.PrivateKeyword) { return; } } } if (this._processedMemberNames.has(memberSymbol.name)) { if (memberSymbol.flags === ts.SymbolFlags.SetAccessor) { // In case of setters, just add them to a list to check later if they have a getter this._setterNames.add(memberSymbol.name); } // Throw an error for duplicate names, because we use names as identifiers // @todo #261549 Define an AEDoc tag to allow defining an identifier for overloaded methods eg. @overload method2 return; } // Proceed to add the member this._processedMemberNames.add(memberSymbol.name); const memberOptions = { context: this.context, declaration: memberDeclaration, declarationSymbol: memberSymbol, jsdocNode: memberDeclaration }; if (memberSymbol.flags & (ts.SymbolFlags.Method | ts.SymbolFlags.Constructor | ts.SymbolFlags.Signature | ts.SymbolFlags.Function)) { this.addMemberItem(new AstMethod_1.default(memberOptions)); } else if (memberSymbol.flags & (ts.SymbolFlags.Property | ts.SymbolFlags.GetAccessor)) { this.addMemberItem(new AstProperty_1.default(memberOptions)); } else { this.reportWarning(`Unsupported member: ${memberSymbol.name}`); } } } exports.default = AstStructuredType; //# sourceMappingURL=AstStructuredType.js.map