UNPKG

@microsoft/api-extractor

Version:

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

177 lines 8.98 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 }); const os = require("os"); const path = require("path"); const node_core_library_1 = require("@microsoft/node-core-library"); const AstItemContainer_1 = require("./ast/AstItemContainer"); const ResolvedApiItem_1 = require("./ResolvedApiItem"); const ApiJsonFile_1 = require("./api/ApiJsonFile"); /** * A loader for locating the ApiItem associated with a given project and API item, or * for locating an AstItem locally. * No processing on the ApiItem orAstItem should be done in this class, this class is only * concerned with communicating state. * The ApiItem can then be used to enforce correct API usage, like enforcing internal. * To use DocItemLoader: provide a projectFolder to construct a instance of the DocItemLoader, * then use DocItemLoader.getItem to retrieve the ApiItem of a particular API item. */ class DocItemLoader { /** * The projectFolder is the top-level folder containing package.json for a project * that we are compiling. */ constructor(projectFolder) { if (!node_core_library_1.FileSystem.exists(path.join(projectFolder, "package.json" /* PackageJson */))) { throw new Error(`An NPM project was not found in the specified folder: ${projectFolder}`); } this._projectFolder = projectFolder; this._cache = new Map(); } /** * {@inheritdoc IReferenceResolver.resolve} */ resolve(apiDefinitionRef, astPackage, warnings) { // We determine if an 'apiDefinitionRef' is local if it has no package name or if the scoped // package name is equal to the current package's scoped package name. if (!apiDefinitionRef.packageName || apiDefinitionRef.toScopePackageString() === astPackage.name) { // Resolution for local references return this.resolveLocalReferences(apiDefinitionRef, astPackage, warnings); } else { // If there was no resolved astItem then try loading from JSON return this.resolveJsonReferences(apiDefinitionRef, warnings); } } /** * Resolution of API definition references in the scenario that the reference given indicates * that we should search within the current AstPackage to resolve. * No processing on the AstItem should be done here, this class is only concerned * with communicating state. */ resolveLocalReferences(apiDefinitionRef, astPackage, warnings) { let astItem = astPackage.getMemberItem(apiDefinitionRef.exportName); // Check if export name was not found if (!astItem) { warnings.push(`Unable to find referenced export \"${apiDefinitionRef.toExportString()}\"`); return undefined; } // If memberName exists then check for the existence of the name if (apiDefinitionRef.memberName) { if (astItem instanceof AstItemContainer_1.AstItemContainer) { const astItemContainer = astItem; // get() returns undefined if there is no match astItem = astItemContainer.getMemberItem(apiDefinitionRef.memberName); } else { // There are no other instances of astItem that has members, // thus there must be a mistake with the apiDefinitionRef. astItem = undefined; } } if (!astItem) { // If we are here, we can be sure there was a problem with the memberName. // memberName was not found, apiDefinitionRef is invalid warnings.push(`Unable to find referenced member \"${apiDefinitionRef.toMemberString()}\"`); return undefined; } return ResolvedApiItem_1.ResolvedApiItem.createFromAstItem(astItem); } /** * Resolution of API definition references in the scenario that the reference given indicates * that we should search outside of this AstPackage and instead search within the JSON API file * that is associated with the apiDefinitionRef. */ resolveJsonReferences(apiDefinitionRef, warnings) { // Check if package can be not found const docPackage = this.getPackage(apiDefinitionRef); if (!docPackage) { // package not found in node_modules warnings.push(`Unable to find a documentation file (\"${apiDefinitionRef.packageName}.api.json\")` + ' for the referenced package'); return undefined; } // found JSON package, now ensure export name is there // hasOwnProperty() not needed for JJU objects if (!(apiDefinitionRef.exportName in docPackage.exports)) { warnings.push(`Unable to find referenced export \"${apiDefinitionRef.toExportString()}\""`); return undefined; } let docItem = docPackage.exports[apiDefinitionRef.exportName]; // If memberName exists then check for the existence of the name if (apiDefinitionRef.memberName) { let member = undefined; switch (docItem.kind) { case 'class': // hasOwnProperty() not needed for JJU objects member = apiDefinitionRef.memberName in docItem.members ? docItem.members[apiDefinitionRef.memberName] : undefined; break; case 'interface': // hasOwnProperty() not needed for JJU objects member = apiDefinitionRef.memberName in docItem.members ? docItem.members[apiDefinitionRef.memberName] : undefined; break; case 'enum': // hasOwnProperty() not needed for JJU objects member = apiDefinitionRef.memberName in docItem.values ? docItem.values[apiDefinitionRef.memberName] : undefined; break; default: // Any other docItem.kind does not have a 'members' property break; } if (member) { docItem = member; } else { // member name was not found, apiDefinitionRef is invalid warnings.push(`Unable to find referenced member \"${apiDefinitionRef.toMemberString()}\"`); return undefined; } } return ResolvedApiItem_1.ResolvedApiItem.createFromJson(docItem); } /** * Attempts to locate and load the IApiPackage object from the project folder's * node modules. If the package already exists in the cache, nothing is done. * * @param apiDefinitionRef - interface with properties pertaining to the API definition reference */ getPackage(apiDefinitionRef) { let cachePackageName = ''; // We concatenate the scopeName and packageName in case there are packageName conflicts if (apiDefinitionRef.scopeName) { cachePackageName = `${apiDefinitionRef.scopeName}/${apiDefinitionRef.packageName}`; } else { cachePackageName = apiDefinitionRef.packageName; } // Check if package exists in cache if (this._cache.has(cachePackageName)) { return this._cache.get(cachePackageName); } // Doesn't exist in cache, attempt to load the json file const apiJsonFilePath = path.join(this._projectFolder, 'node_modules', apiDefinitionRef.scopeName, apiDefinitionRef.packageName, `dist/${apiDefinitionRef.packageName}.api.json`); if (!node_core_library_1.FileSystem.exists(path.join(apiJsonFilePath))) { // Error should be handled by the caller return undefined; } return this.loadPackageIntoCache(apiJsonFilePath, cachePackageName); } /** * Loads the API documentation json file and validates that it conforms to our schema. If it does, * then the json file is saved in the cache and returned. */ loadPackageIntoCache(apiJsonFilePath, cachePackageName) { const astPackage = node_core_library_1.JsonFile.loadAndValidate(apiJsonFilePath, ApiJsonFile_1.ApiJsonFile.jsonSchema, { customErrorHeader: 'The API JSON file does not conform to the expected schema, and may' + os.EOL + 'have been created by an incompatible release of API Extractor:' }); this._cache.set(cachePackageName, astPackage); return astPackage; } } exports.DocItemLoader = DocItemLoader; //# sourceMappingURL=DocItemLoader.js.map