diff --git a/common/changes/@microsoft/tsdoc-config/octogonz-load-from-object_2021-04-16-22-59.json b/common/changes/@microsoft/tsdoc-config/octogonz-load-from-object_2021-04-16-22-59.json new file mode 100644 index 00000000..c6272926 --- /dev/null +++ b/common/changes/@microsoft/tsdoc-config/octogonz-load-from-object_2021-04-16-22-59.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@microsoft/tsdoc-config", + "comment": "Add a new API TSDocConfigFile.loadFromObject()", + "type": "minor" + } + ], + "packageName": "@microsoft/tsdoc-config", + "email": "4673363+octogonz@users.noreply.github.com" +} \ No newline at end of file diff --git a/tsdoc-config/src/TSDocConfigFile.ts b/tsdoc-config/src/TSDocConfigFile.ts index a20265d0..83df8bc0 100644 --- a/tsdoc-config/src/TSDocConfigFile.ts +++ b/tsdoc-config/src/TSDocConfigFile.ts @@ -94,7 +94,8 @@ export class TSDocConfigFile { } /** - * The full path of the file that was attempted to load. + * 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. */ public get filePath(): string { return this._filePath; @@ -276,13 +277,9 @@ export class TSDocConfigFile { this._hasErrors = true; } - private _loadJsonFile(): void { - const configJsonContent: string = fs.readFileSync(this._filePath).toString(); - this._fileMTime = fs.statSync(this._filePath).mtimeMs; + private _loadJsonObject(configJson: IConfigJson): void { this._fileNotFound = false; - const configJson: IConfigJson = jju.parse(configJsonContent, { mode: 'cjson' }); - if (configJson.$schema !== TSDocConfigFile.CURRENT_SCHEMA_URL) { this._reportError({ messageId: TSDocMessageId.ConfigFileUnsupportedSchema, @@ -381,7 +378,12 @@ export class TSDocConfigFile { } alreadyVisitedPaths.add(hashKey); - this._loadJsonFile(); + const configJsonContent: string = fs.readFileSync(this._filePath).toString(); + this._fileMTime = fs.statSync(this._filePath).mtimeMs; + + const configJson: IConfigJson = jju.parse(configJsonContent, { mode: 'cjson' }); + + this._loadJsonObject(configJson); const configFileFolder: string = path.dirname(this.filePath); @@ -462,6 +464,24 @@ export class TSDocConfigFile { 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. + */ + public static loadFromObject(jsonObject: unknown): TSDocConfigFile { + const configFile: TSDocConfigFile = new TSDocConfigFile(); + + configFile._loadJsonObject(jsonObject as IConfigJson); + + 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. */ @@ -563,7 +583,13 @@ export class TSDocConfigFile { return 'No errors.'; } - let result: string = `Errors encountered for ${this.filePath}:\n`; + let result: string; + + if (this.filePath) { + result = `Errors encountered for ${this.filePath}:\n`; + } else { + result = `Errors encountered when loading TSDoc configuration:\n`; + } for (const message of this.log.messages) { result += ` ${message.text}\n`; diff --git a/tsdoc-config/src/__tests__/TSDocConfigFile.test.ts b/tsdoc-config/src/__tests__/TSDocConfigFile.test.ts index c13f2c17..737f028c 100644 --- a/tsdoc-config/src/__tests__/TSDocConfigFile.test.ts +++ b/tsdoc-config/src/__tests__/TSDocConfigFile.test.ts @@ -1,4 +1,4 @@ -import { TSDocConfiguration } from '@microsoft/tsdoc'; +import { TSDocConfiguration, TSDocTagDefinition, TSDocTagSyntaxKind } from '@microsoft/tsdoc'; import * as path from 'path'; import { TSDocConfigFile } from '../TSDocConfigFile'; @@ -207,6 +207,8 @@ test('Load p4', () => { test('Re-serialize p3', () => { const configFile: TSDocConfigFile = TSDocConfigFile.loadForFolder(path.join(__dirname, 'assets/p3')); + expect(configFile.hasErrors).toBe(false); + // This is the data from p3/tsdoc.json, ignoring its "extends" field. expect(configFile.saveToObject()).toMatchInlineSnapshot(` Object { @@ -229,6 +231,8 @@ test('Re-serialize p3 without defaults', () => { parserConfiguration.clear(true); const defaultsConfigFile: TSDocConfigFile = TSDocConfigFile.loadFromParser(parserConfiguration); + expect(defaultsConfigFile.hasErrors).toBe(false); + // This is the default configuration created by the TSDocConfigFile constructor. expect(defaultsConfigFile.saveToObject()).toMatchInlineSnapshot(` Object { @@ -238,6 +242,7 @@ test('Re-serialize p3 without defaults', () => { `); const configFile: TSDocConfigFile = TSDocConfigFile.loadForFolder(path.join(__dirname, 'assets/p3')); + expect(configFile.hasErrors).toBe(false); configFile.noStandardTags = true; configFile.configureParser(parserConfiguration); @@ -275,6 +280,8 @@ test('Re-serialize p3 with defaults', () => { const parserConfiguration: TSDocConfiguration = new TSDocConfiguration(); const defaultsConfigFile: TSDocConfigFile = TSDocConfigFile.loadFromParser(parserConfiguration); + expect(defaultsConfigFile.hasErrors).toBe(false); + // This is the default configuration created by the TSDocConfigFile constructor. expect(defaultsConfigFile.saveToObject()).toMatchInlineSnapshot(` Object { @@ -392,6 +399,7 @@ test('Re-serialize p3 with defaults', () => { `); const configFile: TSDocConfigFile = TSDocConfigFile.loadForFolder(path.join(__dirname, 'assets/p3')); + expect(configFile.hasErrors).toBe(false); configFile.configureParser(parserConfiguration); const mergedConfigFile: TSDocConfigFile = TSDocConfigFile.loadFromParser(parserConfiguration); @@ -532,6 +540,7 @@ test('Re-serialize p3 with defaults', () => { test('Test noStandardTags for p5', () => { const configFile: TSDocConfigFile = TSDocConfigFile.loadForFolder(path.join(__dirname, 'assets/p5')); + expect(configFile.hasErrors).toBe(false); const configuration: TSDocConfiguration = new TSDocConfiguration(); configFile.configureParser(configuration); @@ -542,6 +551,7 @@ test('Test noStandardTags for p5', () => { test('Test noStandardTags for p6', () => { const configFile: TSDocConfigFile = TSDocConfigFile.loadForFolder(path.join(__dirname, 'assets/p6')); + expect(configFile.hasErrors).toBe(false); const configuration: TSDocConfiguration = new TSDocConfiguration(); configFile.configureParser(configuration); @@ -549,3 +559,68 @@ test('Test noStandardTags for p6', () => { // noStandardTags=false because tsdoc.json overrides tsdoc-base1.json expect(configuration.tagDefinitions.length).toBeGreaterThan(0); }); + +test('Test loadFromObject()', () => { + const configuration: TSDocConfiguration = new TSDocConfiguration(); + configuration.clear(true); + + configuration.addTagDefinitions([ + new TSDocTagDefinition({ syntaxKind: TSDocTagSyntaxKind.ModifierTag, tagName: '@tag1' }), + new TSDocTagDefinition({ syntaxKind: TSDocTagSyntaxKind.BlockTag, tagName: '@tag2', allowMultiple: true }), + new TSDocTagDefinition({ syntaxKind: TSDocTagSyntaxKind.InlineTag, tagName: '@tag3', allowMultiple: true }), + ]); + + configuration.setSupportForTag(configuration.tagDefinitions[0], true); + + const configFile: TSDocConfigFile = TSDocConfigFile.loadFromParser(configuration); + expect(configFile.hasErrors).toBe(false); + const jsonObject: unknown = configFile.saveToObject(); + + const configFile2: TSDocConfigFile = TSDocConfigFile.loadFromObject(jsonObject); + expect(configFile2.hasErrors).toBe(false); + const jsonObject2: unknown = configFile2.saveToObject(); + + expect(jsonObject2).toMatchInlineSnapshot(` + Object { + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "noStandardTags": true, + "supportForTags": Object { + "@tag1": true, + }, + "tagDefinitions": Array [ + Object { + "syntaxKind": "modifier", + "tagName": "@tag1", + }, + Object { + "allowMultiple": true, + "syntaxKind": "block", + "tagName": "@tag2", + }, + Object { + "allowMultiple": true, + "syntaxKind": "inline", + "tagName": "@tag3", + }, + ], + } + `); + + expect(jsonObject2).toStrictEqual(jsonObject); +}); + +test('Test loadFromObject() with extends', () => { + const configuration: TSDocConfiguration = new TSDocConfiguration(); + configuration.clear(true); + + const configFile: TSDocConfigFile = TSDocConfigFile.loadFromParser(configuration); + expect(configFile.hasErrors).toBe(false); + const jsonObject: unknown = configFile.saveToObject(); + + // eslint-disable-next-line + (jsonObject as any)['extends'] = ['./some-file.json']; + + expect(() => { + TSDocConfigFile.loadFromObject(jsonObject); + }).toThrowError('The "extends" field cannot be used with TSDocConfigFile.loadFromObject()'); +});