@microsoft/api-extractor
Version:
Validatation, documentation, and auditing for the exported API of a TypeScript package
183 lines (181 loc) • 9.09 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
var fsx = require("fs-extra");
var os = require("os");
var path = require("path");
var ApiItemContainer_1 = require("./definitions/ApiItemContainer");
var ResolvedApiItem_1 = require("./ResolvedApiItem");
var JsonFile_1 = require("./JsonFile");
/**
* A loader for locating the IDocItem associated with a given project and API item, or
* for locating an ApiItem locally.
* No processing on the IDocItem orApiItem should be done in this class, this class is only
* concerned with communicating state.
* The IDocItem 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 IDocItem of a particular API item.
*/
var DocItemLoader = (function () {
/**
* The projectFolder is the top-level folder containing package.json for a project
* that we are compiling.
*/
function DocItemLoader(projectFolder) {
if (!fsx.existsSync(path.join(projectFolder, 'package.json'))) {
throw new Error("An NPM project was not found in the specified folder: " + projectFolder);
}
this._projectFolder = projectFolder;
this._cache = new Map();
}
/**
* {@inheritdoc IReferenceResolver.resolve}
*/
DocItemLoader.prototype.resolve = function (apiDefinitionRef, apiPackage, warnings) {
// We determine if an 'apiDfefinitionRef' 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() === apiPackage.name) {
// Resolution for local references
return this.resolveLocalReferences(apiDefinitionRef, apiPackage, warnings);
}
else {
// If there was no resolved apiItem 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 ApiPackage to resolve.
* No processing on the ApiItem should be done here, this class is only concerned
* with communicating state.
*/
DocItemLoader.prototype.resolveLocalReferences = function (apiDefinitionRef, apiPackage, warnings) {
var apiItem = apiPackage.getMemberItem(apiDefinitionRef.exportName);
// Check if export name was not found
if (!apiItem) {
warnings.push("Unable to find referenced export \"" + apiDefinitionRef.toExportString() + "\"");
return undefined;
}
// If memberName exists then check for the existense of the name
if (apiDefinitionRef.memberName) {
if (apiItem instanceof ApiItemContainer_1.default) {
var apiItemContainer = apiItem;
// get() returns undefined if there is no match
apiItem = apiItemContainer.memberItems.get(apiDefinitionRef.memberName);
}
else {
// There are no other instances of apiItem that has members,
// thus there must be a mistake with the apiDefinitionRef.
apiItem = undefined;
}
}
if (!apiItem) {
// 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.default.createFromApiItem(apiItem);
};
/**
* Resolution of API definition references in the scenario that the reference given indicates
* that we should search outside of this ApiPackage and instead search within the JSON API file
* that is associated with the apiDefinitionRef.
*/
DocItemLoader.prototype.resolveJsonReferences = function (apiDefinitionRef, warnings) {
// Check if package can be not found
var 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;
}
var docItem = docPackage.exports[apiDefinitionRef.exportName];
// If memberName exists then check for the existense of the name
if (apiDefinitionRef.memberName) {
var 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.default.createFromJson(docItem);
};
/**
* Attempts to locate and load the IDocPackage object from the project folder's
* node modules. If the package already exists in the cache, nothing is done.
*
* @param apiDefinitionRef - interface with propropties pertaining to the API definition reference
*/
DocItemLoader.prototype.getPackage = function (apiDefinitionRef) {
var 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
var apiJsonFilePath = path.join(this._projectFolder, 'node_modules', apiDefinitionRef.scopeName, apiDefinitionRef.packageName, "dist/" + apiDefinitionRef.packageName + ".api.json");
if (!fsx.existsSync(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.
*/
DocItemLoader.prototype.loadPackageIntoCache = function (apiJsonFilePath, cachePackageName) {
var apiPackage = JsonFile_1.default.loadJsonFile(apiJsonFilePath);
// Validate that the output conforms to our JSON schema
var apiJsonSchema = JsonFile_1.default.loadJsonFile(path.join(__dirname, './schemas/api-json-schema.json'));
JsonFile_1.default.validateSchema(apiPackage, apiJsonSchema, function (errorDetail) {
var errorMessage = "ApiJsonGenerator validation error - output does not conform to api-json-schema.json:" + os.EOL
+ errorDetail;
console.log(os.EOL + 'ERROR: ' + errorMessage + os.EOL + os.EOL);
throw new Error(errorMessage);
});
this._cache.set(cachePackageName, apiPackage);
return apiPackage;
};
return DocItemLoader;
}());
exports.default = DocItemLoader;
//# sourceMappingURL=DocItemLoader.js.map