683 lines
28 KiB
JavaScript
683 lines
28 KiB
JavaScript
|
"use strict";
|
||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||
|
if (k2 === undefined) k2 = k;
|
||
|
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||
|
}) : (function(o, m, k, k2) {
|
||
|
if (k2 === undefined) k2 = k;
|
||
|
o[k2] = m[k];
|
||
|
}));
|
||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||
|
}) : function(o, v) {
|
||
|
o["default"] = v;
|
||
|
});
|
||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||
|
if (mod && mod.__esModule) return mod;
|
||
|
var result = {};
|
||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||
|
__setModuleDefault(result, mod);
|
||
|
return result;
|
||
|
};
|
||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
|
};
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.TSDocConfigFile = void 0;
|
||
|
const tsdoc_1 = require("@microsoft/tsdoc");
|
||
|
const fs = __importStar(require("fs"));
|
||
|
const resolve = __importStar(require("resolve"));
|
||
|
const path = __importStar(require("path"));
|
||
|
const ajv_1 = __importDefault(require("ajv"));
|
||
|
const jju = __importStar(require("jju"));
|
||
|
const ajv = new ajv_1.default({ verbose: true });
|
||
|
function initializeSchemaValidator() {
|
||
|
const jsonSchemaPath = resolve.sync('@microsoft/tsdoc/schemas/tsdoc.schema.json', { basedir: __dirname });
|
||
|
const jsonSchemaContent = fs.readFileSync(jsonSchemaPath).toString();
|
||
|
const jsonSchema = jju.parse(jsonSchemaContent, { mode: 'cjson' });
|
||
|
return ajv.compile(jsonSchema);
|
||
|
}
|
||
|
// Warning: AJV has a fairly strange API. Each time this function is called, the function object's
|
||
|
// properties get overwritten with the results of the latest validation. Thus we need to be careful
|
||
|
// to read the properties before a subsequent call may occur.
|
||
|
const tsdocSchemaValidator = initializeSchemaValidator();
|
||
|
/**
|
||
|
* Represents an individual `tsdoc.json` file.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
class TSDocConfigFile {
|
||
|
constructor() {
|
||
|
this.log = new tsdoc_1.ParserMessageLog();
|
||
|
this._extendsFiles = [];
|
||
|
this._filePath = '';
|
||
|
this._fileNotFound = false;
|
||
|
this._hasErrors = false;
|
||
|
this._fileMTime = 0;
|
||
|
this._tsdocSchema = '';
|
||
|
this._extendsPaths = [];
|
||
|
this._noStandardTags = undefined;
|
||
|
this._tagDefinitions = [];
|
||
|
this._tagDefinitionNames = new Set();
|
||
|
this._supportForTags = new Map();
|
||
|
}
|
||
|
/**
|
||
|
* Other config files that this file extends from.
|
||
|
*/
|
||
|
get extendsFiles() {
|
||
|
return this._extendsFiles;
|
||
|
}
|
||
|
/**
|
||
|
* The full path of the file that was attempted to load, or an empty string if the configuration was
|
||
|
* loaded from a source that is not a file.
|
||
|
*/
|
||
|
get filePath() {
|
||
|
return this._filePath;
|
||
|
}
|
||
|
/**
|
||
|
* If true, then the TSDocConfigFile object contains an empty state, because the `tsdoc.json` file
|
||
|
* was not found by the loader.
|
||
|
*
|
||
|
* @remarks
|
||
|
* A missing "tsdoc.json" file is not considered an error. It simply means that the defaults will be used.
|
||
|
*/
|
||
|
get fileNotFound() {
|
||
|
return this._fileNotFound;
|
||
|
}
|
||
|
/**
|
||
|
* If true, then at least one error was encountered while loading this file or one of its "extends" files.
|
||
|
*
|
||
|
* @remarks
|
||
|
* You can use {@link TSDocConfigFile.getErrorSummary} to report these errors.
|
||
|
*
|
||
|
* The individual messages can be retrieved from the {@link TSDocConfigFile.log} property of each `TSDocConfigFile`
|
||
|
* object (including the {@link TSDocConfigFile.extendsFiles} tree).
|
||
|
*/
|
||
|
get hasErrors() {
|
||
|
return this._hasErrors;
|
||
|
}
|
||
|
/**
|
||
|
* The `$schema` field from the `tsdoc.json` file.
|
||
|
*/
|
||
|
get tsdocSchema() {
|
||
|
return this._tsdocSchema;
|
||
|
}
|
||
|
/**
|
||
|
* The `extends` field from the `tsdoc.json` file. For the parsed file contents,
|
||
|
* use the `extendsFiles` property instead.
|
||
|
*/
|
||
|
get extendsPaths() {
|
||
|
return this._extendsPaths;
|
||
|
}
|
||
|
/**
|
||
|
* By default, the config file loader will predefine all of the standardized TSDoc tags. To disable this and
|
||
|
* start with a completely empty configuration, set `noStandardTags` to true.
|
||
|
*
|
||
|
* @remarks
|
||
|
* If a config file uses `"extends"` to include settings from base config files, then its setting will
|
||
|
* override any settings from the base config files. If `"noStandardTags"` is not specified, then this
|
||
|
* property will be `undefined`. The config files are applied in the order they are processed (a depth-first
|
||
|
* traversal of the `"extends"` references), and files processed later can override earlier files.
|
||
|
* If no config file specifies `noStandardTags` then the default value is `false`.
|
||
|
*/
|
||
|
get noStandardTags() {
|
||
|
return this._noStandardTags;
|
||
|
}
|
||
|
set noStandardTags(value) {
|
||
|
this._noStandardTags = value;
|
||
|
}
|
||
|
get tagDefinitions() {
|
||
|
return this._tagDefinitions;
|
||
|
}
|
||
|
get supportForTags() {
|
||
|
return this._supportForTags;
|
||
|
}
|
||
|
get supportedHtmlElements() {
|
||
|
return this._supportedHtmlElements && Array.from(this._supportedHtmlElements);
|
||
|
}
|
||
|
get reportUnsupportedHtmlElements() {
|
||
|
return this._reportUnsupportedHtmlElements;
|
||
|
}
|
||
|
set reportUnsupportedHtmlElements(value) {
|
||
|
this._reportUnsupportedHtmlElements = value;
|
||
|
}
|
||
|
/**
|
||
|
* Removes all items from the `tagDefinitions` array.
|
||
|
*/
|
||
|
clearTagDefinitions() {
|
||
|
this._tagDefinitions.length = 0;
|
||
|
this._tagDefinitionNames.clear();
|
||
|
}
|
||
|
/**
|
||
|
* Adds a new item to the `tagDefinitions` array.
|
||
|
*/
|
||
|
addTagDefinition(parameters) {
|
||
|
// This validates the tag name
|
||
|
const tagDefinition = new tsdoc_1.TSDocTagDefinition(parameters);
|
||
|
if (this._tagDefinitionNames.has(tagDefinition.tagNameWithUpperCase)) {
|
||
|
throw new Error(`A tag definition was already added with the tag name "${parameters.tagName}"`);
|
||
|
}
|
||
|
this._tagDefinitionNames.add(tagDefinition.tagName);
|
||
|
this._tagDefinitions.push(tagDefinition);
|
||
|
}
|
||
|
// Similar to addTagDefinition() but reports errors using _reportError()
|
||
|
_addTagDefinitionForLoad(parameters) {
|
||
|
let tagDefinition;
|
||
|
try {
|
||
|
// This validates the tag name
|
||
|
tagDefinition = new tsdoc_1.TSDocTagDefinition(parameters);
|
||
|
}
|
||
|
catch (error) {
|
||
|
this._reportError({
|
||
|
messageId: tsdoc_1.TSDocMessageId.ConfigFileInvalidTagName,
|
||
|
messageText: error.message,
|
||
|
textRange: tsdoc_1.TextRange.empty,
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
if (this._tagDefinitionNames.has(tagDefinition.tagNameWithUpperCase)) {
|
||
|
this._reportError({
|
||
|
messageId: tsdoc_1.TSDocMessageId.ConfigFileDuplicateTagName,
|
||
|
messageText: `The "tagDefinitions" field specifies more than one tag with the name "${parameters.tagName}"`,
|
||
|
textRange: tsdoc_1.TextRange.empty,
|
||
|
});
|
||
|
}
|
||
|
this._tagDefinitionNames.add(tagDefinition.tagNameWithUpperCase);
|
||
|
this._tagDefinitions.push(tagDefinition);
|
||
|
}
|
||
|
/**
|
||
|
* Adds a new item to the `supportedHtmlElements` array.
|
||
|
*/
|
||
|
addSupportedHtmlElement(htmlElement) {
|
||
|
if (!this._supportedHtmlElements) {
|
||
|
this._supportedHtmlElements = new Set();
|
||
|
}
|
||
|
this._supportedHtmlElements.add(htmlElement);
|
||
|
}
|
||
|
/**
|
||
|
* Removes the explicit list of allowed html elements.
|
||
|
*/
|
||
|
clearSupportedHtmlElements() {
|
||
|
this._supportedHtmlElements = undefined;
|
||
|
}
|
||
|
/**
|
||
|
* Removes all entries from the "supportForTags" map.
|
||
|
*/
|
||
|
clearSupportForTags() {
|
||
|
this._supportForTags.clear();
|
||
|
}
|
||
|
/**
|
||
|
* Sets an entry in the "supportForTags" map.
|
||
|
*/
|
||
|
setSupportForTag(tagName, supported) {
|
||
|
tsdoc_1.TSDocTagDefinition.validateTSDocTagName(tagName);
|
||
|
this._supportForTags.set(tagName, supported);
|
||
|
}
|
||
|
/**
|
||
|
* This can be used for cache eviction. It returns true if the modification timestamp has changed for
|
||
|
* any of the files that were read when loading this `TSDocConfigFile`, which indicates that the file should be
|
||
|
* reloaded. It does not consider cases where `TSDocConfigFile.fileNotFound` was `true`.
|
||
|
*
|
||
|
* @remarks
|
||
|
* This can be used for cache eviction. An example eviction strategy might be like this:
|
||
|
*
|
||
|
* - call `checkForModifiedFiles()` once per second, and reload the configuration if it returns true
|
||
|
*
|
||
|
* - otherwise, reload the configuration when it is more than 10 seconds old (to handle less common cases such
|
||
|
* as creation of a missing file, or creation of a file at an earlier location in the search path).
|
||
|
*/
|
||
|
checkForModifiedFiles() {
|
||
|
if (this._checkForModifiedFile()) {
|
||
|
return true;
|
||
|
}
|
||
|
for (const extendsFile of this.extendsFiles) {
|
||
|
if (extendsFile.checkForModifiedFiles()) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
/**
|
||
|
* Checks the last modification time for `TSDocConfigFile.filePath` and returns `true` if it has changed
|
||
|
* since the file was loaded. If the file is missing, this returns `false`. If the timestamp cannot be read,
|
||
|
* then this returns `true`.
|
||
|
*/
|
||
|
_checkForModifiedFile() {
|
||
|
if (this._fileNotFound || !this._filePath) {
|
||
|
return false;
|
||
|
}
|
||
|
try {
|
||
|
const mtimeMs = fs.statSync(this._filePath).mtimeMs;
|
||
|
return mtimeMs !== this._fileMTime;
|
||
|
}
|
||
|
catch (error) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
_reportError(parserMessageParameters) {
|
||
|
this.log.addMessage(new tsdoc_1.ParserMessage(parserMessageParameters));
|
||
|
this._hasErrors = true;
|
||
|
}
|
||
|
_loadJsonObject(configJson) {
|
||
|
if (configJson.$schema !== TSDocConfigFile.CURRENT_SCHEMA_URL) {
|
||
|
this._reportError({
|
||
|
messageId: tsdoc_1.TSDocMessageId.ConfigFileUnsupportedSchema,
|
||
|
messageText: `Unsupported JSON "$schema" value; expecting "${TSDocConfigFile.CURRENT_SCHEMA_URL}"`,
|
||
|
textRange: tsdoc_1.TextRange.empty,
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
const success = tsdocSchemaValidator(configJson);
|
||
|
if (!success) {
|
||
|
const description = ajv.errorsText(tsdocSchemaValidator.errors);
|
||
|
this._reportError({
|
||
|
messageId: tsdoc_1.TSDocMessageId.ConfigFileSchemaError,
|
||
|
messageText: 'Error loading config file: ' + description,
|
||
|
textRange: tsdoc_1.TextRange.empty,
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
this._tsdocSchema = configJson.$schema;
|
||
|
if (configJson.extends) {
|
||
|
this._extendsPaths.push(...configJson.extends);
|
||
|
}
|
||
|
this.noStandardTags = configJson.noStandardTags;
|
||
|
for (const jsonTagDefinition of configJson.tagDefinitions || []) {
|
||
|
let syntaxKind;
|
||
|
switch (jsonTagDefinition.syntaxKind) {
|
||
|
case 'inline':
|
||
|
syntaxKind = tsdoc_1.TSDocTagSyntaxKind.InlineTag;
|
||
|
break;
|
||
|
case 'block':
|
||
|
syntaxKind = tsdoc_1.TSDocTagSyntaxKind.BlockTag;
|
||
|
break;
|
||
|
case 'modifier':
|
||
|
syntaxKind = tsdoc_1.TSDocTagSyntaxKind.ModifierTag;
|
||
|
break;
|
||
|
default:
|
||
|
// The JSON schema should have caught this error
|
||
|
throw new Error('Unexpected tag kind');
|
||
|
}
|
||
|
this._addTagDefinitionForLoad({
|
||
|
tagName: jsonTagDefinition.tagName,
|
||
|
syntaxKind: syntaxKind,
|
||
|
allowMultiple: jsonTagDefinition.allowMultiple,
|
||
|
});
|
||
|
}
|
||
|
if (configJson.supportedHtmlElements) {
|
||
|
this._supportedHtmlElements = new Set();
|
||
|
for (const htmlElement of configJson.supportedHtmlElements) {
|
||
|
this.addSupportedHtmlElement(htmlElement);
|
||
|
}
|
||
|
}
|
||
|
this._reportUnsupportedHtmlElements = configJson.reportUnsupportedHtmlElements;
|
||
|
if (configJson.supportForTags) {
|
||
|
for (const tagName of Object.keys(configJson.supportForTags)) {
|
||
|
const supported = configJson.supportForTags[tagName];
|
||
|
this._supportForTags.set(tagName, supported);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
_loadWithExtends(configFilePath, referencingConfigFile, alreadyVisitedPaths) {
|
||
|
// In case an exception is thrown, start by assuming that the file was not found; we'll revise
|
||
|
// this later upon success
|
||
|
this._fileNotFound = true;
|
||
|
if (!configFilePath) {
|
||
|
this._reportError({
|
||
|
messageId: tsdoc_1.TSDocMessageId.ConfigFileNotFound,
|
||
|
messageText: 'File not found',
|
||
|
textRange: tsdoc_1.TextRange.empty,
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
this._filePath = path.resolve(configFilePath);
|
||
|
if (!fs.existsSync(this._filePath)) {
|
||
|
this._reportError({
|
||
|
messageId: tsdoc_1.TSDocMessageId.ConfigFileNotFound,
|
||
|
messageText: 'File not found',
|
||
|
textRange: tsdoc_1.TextRange.empty,
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
const configJsonContent = fs.readFileSync(this._filePath).toString();
|
||
|
this._fileMTime = fs.statSync(this._filePath).mtimeMs;
|
||
|
this._fileNotFound = false;
|
||
|
const hashKey = fs.realpathSync(this._filePath);
|
||
|
if (referencingConfigFile && alreadyVisitedPaths.has(hashKey)) {
|
||
|
this._reportError({
|
||
|
messageId: tsdoc_1.TSDocMessageId.ConfigFileCyclicExtends,
|
||
|
messageText: `Circular reference encountered for "extends" field of "${referencingConfigFile.filePath}"`,
|
||
|
textRange: tsdoc_1.TextRange.empty,
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
alreadyVisitedPaths.add(hashKey);
|
||
|
let configJson;
|
||
|
try {
|
||
|
configJson = jju.parse(configJsonContent, { mode: 'cjson' });
|
||
|
}
|
||
|
catch (e) {
|
||
|
this._reportError({
|
||
|
messageId: tsdoc_1.TSDocMessageId.ConfigInvalidJson,
|
||
|
messageText: 'Error parsing JSON input: ' + e.message,
|
||
|
textRange: tsdoc_1.TextRange.empty,
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
this._loadJsonObject(configJson);
|
||
|
const configFileFolder = path.dirname(this.filePath);
|
||
|
for (const extendsField of this.extendsPaths) {
|
||
|
let resolvedExtendsPath;
|
||
|
try {
|
||
|
resolvedExtendsPath = resolve.sync(extendsField, { basedir: configFileFolder });
|
||
|
}
|
||
|
catch (e) {
|
||
|
this._reportError({
|
||
|
messageId: tsdoc_1.TSDocMessageId.ConfigFileUnresolvedExtends,
|
||
|
messageText: `Unable to resolve "extends" reference to "${extendsField}": ` + e.message,
|
||
|
textRange: tsdoc_1.TextRange.empty,
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
const baseConfigFile = new TSDocConfigFile();
|
||
|
baseConfigFile._loadWithExtends(resolvedExtendsPath, this, alreadyVisitedPaths);
|
||
|
if (baseConfigFile.fileNotFound) {
|
||
|
this._reportError({
|
||
|
messageId: tsdoc_1.TSDocMessageId.ConfigFileUnresolvedExtends,
|
||
|
messageText: `Unable to resolve "extends" reference to "${extendsField}"`,
|
||
|
textRange: tsdoc_1.TextRange.empty,
|
||
|
});
|
||
|
}
|
||
|
this._extendsFiles.push(baseConfigFile);
|
||
|
if (baseConfigFile.hasErrors) {
|
||
|
this._hasErrors = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* For the given folder, look for the relevant tsdoc.json file (if any), and return its path.
|
||
|
*
|
||
|
* @param folderPath - the path to a folder where the search should start
|
||
|
* @returns the (possibly relative) path to tsdoc.json, or an empty string if not found
|
||
|
*/
|
||
|
static findConfigPathForFolder(folderPath) {
|
||
|
if (folderPath) {
|
||
|
let foundFolder = folderPath;
|
||
|
for (;;) {
|
||
|
const tsconfigJsonPath = path.join(foundFolder, 'tsconfig.json');
|
||
|
if (fs.existsSync(tsconfigJsonPath)) {
|
||
|
// Stop when we reach a folder containing tsconfig.json
|
||
|
return path.join(foundFolder, TSDocConfigFile.FILENAME);
|
||
|
}
|
||
|
const packageJsonPath = path.join(foundFolder, 'package.json');
|
||
|
if (fs.existsSync(packageJsonPath)) {
|
||
|
// Stop when we reach a folder containing package.json; this avoids crawling out of the current package
|
||
|
return path.join(foundFolder, TSDocConfigFile.FILENAME);
|
||
|
}
|
||
|
const previousFolder = foundFolder;
|
||
|
foundFolder = path.dirname(foundFolder);
|
||
|
if (!foundFolder || foundFolder === previousFolder) {
|
||
|
// Give up if we reach the filesystem root directory
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return '';
|
||
|
}
|
||
|
/**
|
||
|
* Calls `TSDocConfigFile.findConfigPathForFolder()` to find the relevant tsdoc.json config file, if one exists.
|
||
|
* Then calls `TSDocConfigFile.findConfigPathForFolder()` to return the loaded result.
|
||
|
*
|
||
|
* @remarks
|
||
|
* This API does not report loading errors by throwing exceptions. Instead, the caller is expected to check
|
||
|
* for errors using {@link TSDocConfigFile.hasErrors}, {@link TSDocConfigFile.log},
|
||
|
* or {@link TSDocConfigFile.getErrorSummary}.
|
||
|
*
|
||
|
* @param folderPath - the path to a folder where the search should start
|
||
|
*/
|
||
|
static loadForFolder(folderPath) {
|
||
|
const rootConfigPath = TSDocConfigFile.findConfigPathForFolder(folderPath);
|
||
|
return TSDocConfigFile.loadFile(rootConfigPath);
|
||
|
}
|
||
|
/**
|
||
|
* Loads the specified tsdoc.json and any base files that it refers to using the "extends" option.
|
||
|
*
|
||
|
* @remarks
|
||
|
* This API does not report loading errors by throwing exceptions. Instead, the caller is expected to check
|
||
|
* for errors using {@link TSDocConfigFile.hasErrors}, {@link TSDocConfigFile.log},
|
||
|
* or {@link TSDocConfigFile.getErrorSummary}.
|
||
|
*
|
||
|
* @param tsdocJsonFilePath - the path to the tsdoc.json config file
|
||
|
*/
|
||
|
static loadFile(tsdocJsonFilePath) {
|
||
|
const configFile = new TSDocConfigFile();
|
||
|
const alreadyVisitedPaths = new Set();
|
||
|
configFile._loadWithExtends(tsdocJsonFilePath, undefined, alreadyVisitedPaths);
|
||
|
return configFile;
|
||
|
}
|
||
|
/**
|
||
|
* Loads the object state from a JSON-serializable object as produced by {@link TSDocConfigFile.saveToObject}.
|
||
|
*
|
||
|
* @remarks
|
||
|
* The serialized object has the same structure as `tsdoc.json`; however the `"extends"` field is not allowed.
|
||
|
*
|
||
|
* This API does not report loading errors by throwing exceptions. Instead, the caller is expected to check
|
||
|
* for errors using {@link TSDocConfigFile.hasErrors}, {@link TSDocConfigFile.log},
|
||
|
* or {@link TSDocConfigFile.getErrorSummary}.
|
||
|
*/
|
||
|
static loadFromObject(jsonObject) {
|
||
|
const configFile = new TSDocConfigFile();
|
||
|
configFile._loadJsonObject(jsonObject);
|
||
|
if (configFile.extendsPaths.length > 0) {
|
||
|
throw new Error('The "extends" field cannot be used with TSDocConfigFile.loadFromObject()');
|
||
|
}
|
||
|
return configFile;
|
||
|
}
|
||
|
/**
|
||
|
* Initializes a TSDocConfigFile object using the state from the provided `TSDocConfiguration` object.
|
||
|
*
|
||
|
* @remarks
|
||
|
* This API does not report loading errors by throwing exceptions. Instead, the caller is expected to check
|
||
|
* for errors using {@link TSDocConfigFile.hasErrors}, {@link TSDocConfigFile.log},
|
||
|
* or {@link TSDocConfigFile.getErrorSummary}.
|
||
|
*/
|
||
|
static loadFromParser(configuration) {
|
||
|
const configFile = new TSDocConfigFile();
|
||
|
// The standard tags will be mixed together with custom definitions,
|
||
|
// so set noStandardTags=true to avoid defining them twice.
|
||
|
configFile.noStandardTags = true;
|
||
|
for (const tagDefinition of configuration.tagDefinitions) {
|
||
|
configFile.addTagDefinition({
|
||
|
syntaxKind: tagDefinition.syntaxKind,
|
||
|
tagName: tagDefinition.tagName,
|
||
|
allowMultiple: tagDefinition.allowMultiple,
|
||
|
});
|
||
|
}
|
||
|
for (const tagDefinition of configuration.supportedTagDefinitions) {
|
||
|
configFile.setSupportForTag(tagDefinition.tagName, true);
|
||
|
}
|
||
|
for (const htmlElement of configuration.supportedHtmlElements) {
|
||
|
configFile.addSupportedHtmlElement(htmlElement);
|
||
|
}
|
||
|
configFile.reportUnsupportedHtmlElements = configuration.validation.reportUnsupportedHtmlElements;
|
||
|
return configFile;
|
||
|
}
|
||
|
/**
|
||
|
* Writes the config file content to a JSON file with the specified file path.
|
||
|
*/
|
||
|
saveFile(jsonFilePath) {
|
||
|
const jsonObject = this.saveToObject();
|
||
|
const jsonContent = JSON.stringify(jsonObject, undefined, 2);
|
||
|
fs.writeFileSync(jsonFilePath, jsonContent);
|
||
|
}
|
||
|
/**
|
||
|
* Writes the object state into a JSON-serializable object.
|
||
|
*/
|
||
|
saveToObject() {
|
||
|
const configJson = {
|
||
|
$schema: TSDocConfigFile.CURRENT_SCHEMA_URL,
|
||
|
};
|
||
|
if (this.noStandardTags !== undefined) {
|
||
|
configJson.noStandardTags = this.noStandardTags;
|
||
|
}
|
||
|
if (this.tagDefinitions.length > 0) {
|
||
|
configJson.tagDefinitions = [];
|
||
|
for (const tagDefinition of this.tagDefinitions) {
|
||
|
configJson.tagDefinitions.push(TSDocConfigFile._serializeTagDefinition(tagDefinition));
|
||
|
}
|
||
|
}
|
||
|
if (this.supportForTags.size > 0) {
|
||
|
configJson.supportForTags = {};
|
||
|
this.supportForTags.forEach((supported, tagName) => {
|
||
|
configJson.supportForTags[tagName] = supported;
|
||
|
});
|
||
|
}
|
||
|
if (this.supportedHtmlElements) {
|
||
|
configJson.supportedHtmlElements = [...this.supportedHtmlElements];
|
||
|
}
|
||
|
if (this._reportUnsupportedHtmlElements !== undefined) {
|
||
|
configJson.reportUnsupportedHtmlElements = this._reportUnsupportedHtmlElements;
|
||
|
}
|
||
|
return configJson;
|
||
|
}
|
||
|
static _serializeTagDefinition(tagDefinition) {
|
||
|
let syntaxKind;
|
||
|
switch (tagDefinition.syntaxKind) {
|
||
|
case tsdoc_1.TSDocTagSyntaxKind.InlineTag:
|
||
|
syntaxKind = 'inline';
|
||
|
break;
|
||
|
case tsdoc_1.TSDocTagSyntaxKind.BlockTag:
|
||
|
syntaxKind = 'block';
|
||
|
break;
|
||
|
case tsdoc_1.TSDocTagSyntaxKind.ModifierTag:
|
||
|
syntaxKind = 'modifier';
|
||
|
break;
|
||
|
default:
|
||
|
throw new Error('Unimplemented TSDocTagSyntaxKind');
|
||
|
}
|
||
|
const tagConfigJson = {
|
||
|
tagName: tagDefinition.tagName,
|
||
|
syntaxKind,
|
||
|
};
|
||
|
if (tagDefinition.allowMultiple) {
|
||
|
tagConfigJson.allowMultiple = true;
|
||
|
}
|
||
|
return tagConfigJson;
|
||
|
}
|
||
|
/**
|
||
|
* Returns a report of any errors that occurred while attempting to load this file or any files
|
||
|
* referenced via the "extends" field.
|
||
|
*
|
||
|
* @remarks
|
||
|
* Use {@link TSDocConfigFile.hasErrors} to determine whether any errors occurred.
|
||
|
*/
|
||
|
getErrorSummary() {
|
||
|
if (!this._hasErrors) {
|
||
|
return 'No errors.';
|
||
|
}
|
||
|
let result = '';
|
||
|
if (this.log.messages.length > 0) {
|
||
|
const errorNoun = this.log.messages.length > 1 ? 'Errors' : 'Error';
|
||
|
if (this.filePath) {
|
||
|
result += `${errorNoun} encountered for ${this.filePath}:\n`;
|
||
|
}
|
||
|
else {
|
||
|
result += `${errorNoun} encountered when loading TSDoc configuration:\n`;
|
||
|
}
|
||
|
for (const message of this.log.messages) {
|
||
|
result += ` ${message.text}\n`;
|
||
|
}
|
||
|
}
|
||
|
for (const extendsFile of this.extendsFiles) {
|
||
|
if (extendsFile.hasErrors) {
|
||
|
if (result !== '') {
|
||
|
result += '\n';
|
||
|
}
|
||
|
result += extendsFile.getErrorSummary();
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
/**
|
||
|
* Applies the settings from this config file to a TSDoc parser configuration.
|
||
|
* Any `extendsFile` settings will also applied.
|
||
|
*
|
||
|
* @remarks
|
||
|
* Additional validation is performed during this operation. The caller is expected to check for errors
|
||
|
* using {@link TSDocConfigFile.hasErrors}, {@link TSDocConfigFile.log}, or {@link TSDocConfigFile.getErrorSummary}.
|
||
|
*/
|
||
|
configureParser(configuration) {
|
||
|
if (this._getNoStandardTagsWithExtends()) {
|
||
|
// Do not define standard tags
|
||
|
configuration.clear(true);
|
||
|
}
|
||
|
else {
|
||
|
// Define standard tags (the default behavior)
|
||
|
configuration.clear(false);
|
||
|
}
|
||
|
this.updateParser(configuration);
|
||
|
}
|
||
|
/**
|
||
|
* This is the same as {@link configureParser}, but it preserves any previous state.
|
||
|
*
|
||
|
* @remarks
|
||
|
* Additional validation is performed during this operation. The caller is expected to check for errors
|
||
|
* using {@link TSDocConfigFile.hasErrors}, {@link TSDocConfigFile.log}, or {@link TSDocConfigFile.getErrorSummary}.
|
||
|
*/
|
||
|
updateParser(configuration) {
|
||
|
// First apply the base config files
|
||
|
for (const extendsFile of this.extendsFiles) {
|
||
|
extendsFile.updateParser(configuration);
|
||
|
}
|
||
|
// Then apply this one
|
||
|
for (const tagDefinition of this.tagDefinitions) {
|
||
|
configuration.addTagDefinition(tagDefinition);
|
||
|
}
|
||
|
this.supportForTags.forEach((supported, tagName) => {
|
||
|
const tagDefinition = configuration.tryGetTagDefinition(tagName);
|
||
|
if (tagDefinition) {
|
||
|
// Note that setSupportForTag() automatically enables configuration.validation.reportUnsupportedTags
|
||
|
configuration.setSupportForTag(tagDefinition, supported);
|
||
|
}
|
||
|
else {
|
||
|
// Note that this validation may depend partially on the preexisting state of the TSDocConfiguration
|
||
|
// object, so it cannot be performed during the TSConfigFile.loadFile() stage.
|
||
|
this._reportError({
|
||
|
messageId: tsdoc_1.TSDocMessageId.ConfigFileUndefinedTag,
|
||
|
messageText: `The "supportForTags" field refers to an undefined tag ${JSON.stringify(tagName)}.`,
|
||
|
textRange: tsdoc_1.TextRange.empty,
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
if (this.supportedHtmlElements) {
|
||
|
configuration.setSupportedHtmlElements([...this.supportedHtmlElements]);
|
||
|
}
|
||
|
if (this._reportUnsupportedHtmlElements === false) {
|
||
|
configuration.validation.reportUnsupportedHtmlElements = false;
|
||
|
}
|
||
|
else if (this._reportUnsupportedHtmlElements === true) {
|
||
|
configuration.validation.reportUnsupportedHtmlElements = true;
|
||
|
}
|
||
|
}
|
||
|
_getNoStandardTagsWithExtends() {
|
||
|
if (this.noStandardTags !== undefined) {
|
||
|
return this.noStandardTags;
|
||
|
}
|
||
|
// This config file does not specify "noStandardTags", so consider any base files referenced using "extends"
|
||
|
let result = undefined;
|
||
|
for (const extendsFile of this.extendsFiles) {
|
||
|
const extendedValue = extendsFile._getNoStandardTagsWithExtends();
|
||
|
if (extendedValue !== undefined) {
|
||
|
result = extendedValue;
|
||
|
}
|
||
|
}
|
||
|
if (result === undefined) {
|
||
|
// If no config file specifies noStandardTags, then it defaults to false
|
||
|
result = false;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
exports.TSDocConfigFile = TSDocConfigFile;
|
||
|
TSDocConfigFile.FILENAME = 'tsdoc.json';
|
||
|
TSDocConfigFile.CURRENT_SCHEMA_URL = 'https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json';
|
||
|
//# sourceMappingURL=TSDocConfigFile.js.map
|