diff --git a/apps/api-extractor/src/analyzer/TypeScriptInternals.ts b/apps/api-extractor/src/analyzer/TypeScriptInternals.ts index ae3c0e04316..32ab70795bd 100644 --- a/apps/api-extractor/src/analyzer/TypeScriptInternals.ts +++ b/apps/api-extractor/src/analyzer/TypeScriptInternals.ts @@ -138,4 +138,12 @@ export class TypeScriptInternals { } return resolver; } + + /** + * Returns whether a variable is declared with the const keyword + */ + public static isVarConst(node: ts.VariableDeclaration | ts.VariableDeclarationList): boolean { + // Compiler internal: https://github.com/microsoft/TypeScript/blob/71286e3d49c10e0e99faac360a6bbd40f12db7b6/src/compiler/utilities.ts#L925 + return (ts as any).isVarConst(node); + } } diff --git a/apps/api-extractor/src/generators/ApiModelGenerator.ts b/apps/api-extractor/src/generators/ApiModelGenerator.ts index 6c508a38b0f..7b3820653c0 100644 --- a/apps/api-extractor/src/generators/ApiModelGenerator.ts +++ b/apps/api-extractor/src/generators/ApiModelGenerator.ts @@ -43,6 +43,7 @@ import { DeclarationMetadata } from '../collector/DeclarationMetadata'; import { AstNamespaceImport } from '../analyzer/AstNamespaceImport'; import { AstEntity } from '../analyzer/AstEntity'; import { AstModule } from '../analyzer/AstModule'; +import { TypeScriptInternals } from '../analyzer/TypeScriptInternals'; export class ApiModelGenerator { private readonly _collector: Collector; @@ -851,6 +852,7 @@ export class ApiModelGenerator { const isOptional: boolean = (astDeclaration.astSymbol.followedSymbol.flags & ts.SymbolFlags.Optional) !== 0; const isProtected: boolean = (astDeclaration.modifierFlags & ts.ModifierFlags.Protected) !== 0; + const isReadonly: boolean = this._determineReadonly(astDeclaration); apiProperty = new ApiProperty({ name, @@ -859,6 +861,7 @@ export class ApiModelGenerator { isProtected, isStatic, isOptional, + isReadonly, excerptTokens, propertyTypeTokenRange }); @@ -895,6 +898,7 @@ export class ApiModelGenerator { const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; const isOptional: boolean = (astDeclaration.astSymbol.followedSymbol.flags & ts.SymbolFlags.Optional) !== 0; + const isReadonly: boolean = this._determineReadonly(astDeclaration); apiPropertySignature = new ApiPropertySignature({ name, @@ -902,7 +906,8 @@ export class ApiModelGenerator { releaseTag, isOptional, excerptTokens, - propertyTypeTokenRange + propertyTypeTokenRange, + isReadonly }); parentApiItem.addMember(apiPropertySignature); @@ -981,8 +986,16 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; + const isReadonly: boolean = this._determineReadonly(astDeclaration); - apiVariable = new ApiVariable({ name, docComment, releaseTag, excerptTokens, variableTypeTokenRange }); + apiVariable = new ApiVariable({ + name, + docComment, + releaseTag, + excerptTokens, + variableTypeTokenRange, + isReadonly + }); parentApiItem.addMember(apiVariable); } @@ -1055,4 +1068,22 @@ export class ApiModelGenerator { } return parameters; } + + private _determineReadonly(astDeclaration: AstDeclaration): boolean { + const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); + const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; + const declarationMetadata: DeclarationMetadata = this._collector.fetchDeclarationMetadata(astDeclaration); + //Line 1: sees whether the readonly or const modifiers are present + //Line 2: sees if the TSDoc comment for @readonly is present + //Line 3: sees whether a getter is present for a property with no setter + //Line 4: sees if the var declaration has Const keyword + return ( + (astDeclaration.modifierFlags & (ts.ModifierFlags.Readonly + ts.ModifierFlags.Const)) !== 0 || + (docComment !== undefined && docComment.modifierTagSet.hasTagName('@readonly')) || + (declarationMetadata.ancillaryDeclarations.length === 0 && + astDeclaration.declaration.kind === ts.SyntaxKind.GetAccessor) || + (ts.isVariableDeclaration(astDeclaration.declaration) && + TypeScriptInternals.isVarConst(astDeclaration.declaration)) + ); + } } diff --git a/build-tests/api-documenter-test/etc/api-documenter-test.api.json b/build-tests/api-documenter-test/etc/api-documenter-test.api.json index c74a5c89274..9431f20e9fd 100644 --- a/build-tests/api-documenter-test/etc/api-documenter-test.api.json +++ b/build-tests/api-documenter-test/etc/api-documenter-test.api.json @@ -209,6 +209,7 @@ "text": "number" } ], + "isReadonly": true, "releaseTag": "Public", "name": "constVariable", "variableTypeTokenRange": { @@ -248,6 +249,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "creationDate", @@ -299,8 +301,8 @@ "text": "constructor();" } ], - "isProtected": false, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [] }, @@ -322,8 +324,8 @@ "text": ");" } ], - "isProtected": false, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 2, "parameters": [ { @@ -401,16 +403,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "deprecatedExample" }, { @@ -447,14 +449,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 5, "endIndex": 6 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [ { @@ -474,6 +475,7 @@ "isOptional": false } ], + "isOptional": false, "name": "exampleFunction" }, { @@ -502,14 +504,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 3, "endIndex": 4 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 2, "parameters": [ { @@ -521,6 +522,7 @@ "isOptional": false } ], + "isOptional": false, "name": "exampleFunction" }, { @@ -571,14 +573,26 @@ "text": ";" } ], - "isOptional": false, + "typeParameters": [ + { + "typeParameterName": "T", + "constraintTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "defaultTypeTokenRange": { + "startIndex": 4, + "endIndex": 5 + } + } + ], "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 8, "endIndex": 9 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [ { @@ -590,19 +604,7 @@ "isOptional": false } ], - "typeParameters": [ - { - "typeParameterName": "T", - "constraintTokenRange": { - "startIndex": 1, - "endIndex": 2 - }, - "defaultTypeTokenRange": { - "startIndex": 4, - "endIndex": 5 - } - } - ], + "isOptional": false, "name": "genericWithConstraintAndDefault" }, { @@ -623,16 +625,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "interestingEdgeCases" }, { @@ -654,6 +656,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "malformedEvent", @@ -683,6 +686,7 @@ "text": ";" } ], + "isReadonly": true, "isOptional": false, "releaseTag": "Public", "name": "modifiedEvent", @@ -719,14 +723,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 3, "endIndex": 4 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [ { @@ -738,6 +741,7 @@ "isOptional": true } ], + "isOptional": false, "name": "optionalParamFunction" }, { @@ -758,6 +762,7 @@ "text": ";" } ], + "isReadonly": true, "isOptional": false, "releaseTag": "Public", "name": "readonlyProperty", @@ -787,6 +792,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "regularProperty", @@ -797,6 +803,35 @@ "isStatic": false, "isProtected": false }, + { + "kind": "Property", + "canonicalReference": "api-documenter-test!DocClass1.staticReadonlyThing:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "static readonly staticReadonlyThing: " + }, + { + "kind": "Content", + "text": "boolean" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": true, + "isOptional": false, + "releaseTag": "Public", + "name": "staticReadonlyThing", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": true, + "isProtected": false + }, { "kind": "Method", "canonicalReference": "api-documenter-test!DocClass1.sumWithExample:member(1)", @@ -831,14 +866,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": true, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 5, "endIndex": 6 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [ { @@ -858,6 +892,7 @@ "isOptional": false } ], + "isOptional": false, "name": "sumWithExample" }, { @@ -878,16 +913,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "tableExample" }, { @@ -912,6 +947,7 @@ "text": "\n\nset writeableProperty(value: string);" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "writeableProperty", @@ -940,6 +976,7 @@ "text": ");" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "writeonlyProperty", @@ -1200,6 +1237,7 @@ "text": "unique symbol" } ], + "isReadonly": true, "releaseTag": "Public", "name": "example", "variableTypeTokenRange": { @@ -1469,6 +1507,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "regularProperty", @@ -1569,6 +1608,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "\"[not.a.symbol]\"", @@ -1604,6 +1644,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "[EcmaSmbols.example]", @@ -1743,6 +1784,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "redundantQuotes", @@ -1785,6 +1827,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "Context", @@ -1816,6 +1859,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "generic", @@ -1842,6 +1886,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "numberOrFunction", @@ -1868,6 +1913,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "stringOrNumber", @@ -1910,6 +1956,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "regularProperty", @@ -1957,6 +2004,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "arrayProperty", @@ -2051,6 +2099,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "intersectionProperty", @@ -2077,6 +2126,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "regularProperty", @@ -2121,6 +2171,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "tupleProperty", @@ -2161,6 +2212,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "typeReferenceProperty", @@ -2197,6 +2249,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "unionProperty", @@ -2239,6 +2292,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": true, "releaseTag": "Public", "name": "optionalField", @@ -2293,6 +2347,7 @@ "text": ";" } ], + "isReadonly": true, "isOptional": true, "releaseTag": "Public", "name": "optionalReadonlyField", @@ -2319,6 +2374,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": true, "releaseTag": "Public", "name": "optionalUndocumentedField", @@ -2416,6 +2472,7 @@ "text": "boolean" } ], + "isReadonly": false, "releaseTag": "Public", "name": "nestedVariable", "variableTypeTokenRange": { @@ -2464,14 +2521,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 3, "endIndex": 4 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [ { @@ -2483,6 +2539,7 @@ "isOptional": false } ], + "isOptional": false, "name": "addHandler" } ], diff --git a/build-tests/api-documenter-test/etc/api-documenter-test.api.md b/build-tests/api-documenter-test/etc/api-documenter-test.api.md index 828c30e2583..e9e1412b999 100644 --- a/build-tests/api-documenter-test/etc/api-documenter-test.api.md +++ b/build-tests/api-documenter-test/etc/api-documenter-test.api.md @@ -44,6 +44,8 @@ export class DocClass1 extends DocBaseClass implements IDocInterface1, IDocInter // (undocumented) get readonlyProperty(): string; regularProperty: SystemEvent; + // (undocumented) + static readonly staticReadonlyThing: boolean; static sumWithExample(x: number, y: number): number; tableExample(): void; // (undocumented) diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclass1.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclass1.md index 0c0a9e42f51..e6c3fa4b49d 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclass1.md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclass1.md @@ -37,6 +37,7 @@ The constructor for this class is marked as internal. Third-party code should no | --- | --- | --- | --- | | [readonlyProperty](./api-documenter-test.docclass1.readonlyproperty.md) | | string | | | [regularProperty](./api-documenter-test.docclass1.regularproperty.md) | | [SystemEvent](./api-documenter-test.systemevent.md) | This is a regular property that happens to use the SystemEvent type. | +| [staticReadonlyThing](./api-documenter-test.docclass1.staticreadonlything.md) | static | boolean | | | [writeableProperty](./api-documenter-test.docclass1.writeableproperty.md) | | string | | | [writeonlyProperty](./api-documenter-test.docclass1.writeonlyproperty.md) | | string | API Extractor will surface an ae-missing-getter finding for this property. | diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclass1.staticreadonlything.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclass1.staticreadonlything.md new file mode 100644 index 00000000000..ecb46a0668b --- /dev/null +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclass1.staticreadonlything.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [DocClass1](./api-documenter-test.docclass1.md) > [staticReadonlyThing](./api-documenter-test.docclass1.staticreadonlything.md) + +## DocClass1.staticReadonlyThing property + +Signature: + +```typescript +static readonly staticReadonlyThing: boolean; +``` diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclass1.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclass1.yml index b02c6cb4ff3..5914b86ab2b 100644 --- a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclass1.yml +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclass1.yml @@ -45,6 +45,19 @@ properties: content: 'regularProperty: SystemEvent;' return: type: '' + - name: staticReadonlyThing + uid: 'api-documenter-test!DocClass1.staticReadonlyThing:member' + package: api-documenter-test! + fullName: staticReadonlyThing + summary: '' + remarks: '' + example: [] + isPreview: false + isDeprecated: false + syntax: + content: 'static readonly staticReadonlyThing: boolean;' + return: + type: boolean - name: writeableProperty uid: 'api-documenter-test!DocClass1#writeableProperty:member' package: api-documenter-test! diff --git a/build-tests/api-documenter-test/src/DocClass1.ts b/build-tests/api-documenter-test/src/DocClass1.ts index 313a0f45a95..08c5764ca10 100644 --- a/build-tests/api-documenter-test/src/DocClass1.ts +++ b/build-tests/api-documenter-test/src/DocClass1.ts @@ -169,6 +169,8 @@ export class DocClass1 extends DocBaseClass implements IDocInterface1, IDocInter super(); } + static readonly staticReadonlyThing: boolean; + /** * This is an overloaded function. * @param a - the first string diff --git a/build-tests/api-extractor-scenarios/config/build-config.json b/build-tests/api-extractor-scenarios/config/build-config.json index 9a5de053b85..235cccb9382 100644 --- a/build-tests/api-extractor-scenarios/config/build-config.json +++ b/build-tests/api-extractor-scenarios/config/build-config.json @@ -36,6 +36,7 @@ "internationalCharacters", "namedDefaultImport", "preapproved", + "readonlyDeclarations", "spanSorting", "typeOf", "typeOf2", diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/apiItemKinds/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/apiItemKinds/api-extractor-scenarios.api.json index 1ad471e5698..ddc84f4d697 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/apiItemKinds/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/apiItemKinds/api-extractor-scenarios.api.json @@ -201,16 +201,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "member" } ], @@ -255,14 +255,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 3, "endIndex": 4 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [ { @@ -274,6 +273,7 @@ "isOptional": false } ], + "isOptional": false, "name": "method1" }, { @@ -303,16 +303,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 4 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "method2" } ], @@ -427,6 +427,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "member", @@ -465,6 +466,7 @@ "text": "object[]" } ], + "isReadonly": false, "releaseTag": "Public", "name": "constVariable", "variableTypeTokenRange": { @@ -486,6 +488,7 @@ "text": "object[]" } ], + "isReadonly": false, "releaseTag": "Public", "name": "variable", "variableTypeTokenRange": { @@ -604,16 +607,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "member" }, { @@ -642,14 +645,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 3, "endIndex": 4 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [ { @@ -661,6 +663,7 @@ "isOptional": true } ], + "isOptional": false, "name": "optionalParamMethod" }, { @@ -681,6 +684,7 @@ "text": ";" } ], + "isReadonly": true, "isOptional": false, "releaseTag": "Public", "name": "readonlyProperty", @@ -701,6 +705,7 @@ "text": "readonly someReadonlyProp = 5;" } ], + "isReadonly": true, "isOptional": false, "releaseTag": "Public", "name": "someReadonlyProp", @@ -729,6 +734,7 @@ "text": ";" } ], + "isReadonly": true, "isOptional": false, "releaseTag": "Public", "name": "someReadonlyPropWithType", @@ -761,6 +767,7 @@ "text": "\n\nset writeableProperty(value: string);" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "writeableProperty", @@ -788,6 +795,7 @@ "text": "string" } ], + "isReadonly": true, "releaseTag": "Public", "name": "VARIABLE", "variableTypeTokenRange": { diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/bundledPackages/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/bundledPackages/api-extractor-scenarios.api.json index ab02095254d..bccae8ebf56 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/bundledPackages/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/bundledPackages/api-extractor-scenarios.api.json @@ -272,6 +272,7 @@ "text": ";" } ], + "isReadonly": true, "isOptional": false, "releaseTag": "Public", "name": "readonlyProperty", @@ -300,6 +301,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "writeableProperty", diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/circularImport/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/circularImport/api-extractor-scenarios.api.json index 137bc2cf3cb..115ef5174f8 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/circularImport/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/circularImport/api-extractor-scenarios.api.json @@ -202,6 +202,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "containingFolder", @@ -251,6 +252,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "containingFolder", @@ -284,6 +286,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "files", diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/circularImport2/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/circularImport2/api-extractor-scenarios.api.json index 52cb2b531f9..ee1019ead38 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/circularImport2/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/circularImport2/api-extractor-scenarios.api.json @@ -232,6 +232,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "containingFolder", @@ -281,6 +282,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "containingFolder", @@ -314,6 +316,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "files", diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/defaultExportOfEntryPoint2/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/defaultExportOfEntryPoint2/api-extractor-scenarios.api.json index e7e25bbfac5..cf3343522f6 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/defaultExportOfEntryPoint2/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/defaultExportOfEntryPoint2/api-extractor-scenarios.api.json @@ -184,6 +184,7 @@ "text": "() => void" } ], + "isReadonly": true, "releaseTag": "Public", "name": "defaultFunctionStatement", "variableTypeTokenRange": { diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/defaultExportOfEntryPoint4/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/defaultExportOfEntryPoint4/api-extractor-scenarios.api.json index 8a31e30a029..6ad2778b65e 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/defaultExportOfEntryPoint4/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/defaultExportOfEntryPoint4/api-extractor-scenarios.api.json @@ -184,6 +184,7 @@ "text": "\"literal\"" } ], + "isReadonly": true, "releaseTag": "Public", "name": "_default", "variableTypeTokenRange": { diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences/api-extractor-scenarios.api.json index 760eaee1338..82a4e4577d3 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences/api-extractor-scenarios.api.json @@ -276,14 +276,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 3, "endIndex": 4 }, "releaseTag": "Beta", + "isProtected": false, "overloadIndex": 1, "parameters": [ { @@ -295,6 +294,7 @@ "isOptional": false } ], + "isOptional": false, "name": "myMethod" } ], diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences2/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences2/api-extractor-scenarios.api.json index 185efd3af43..d9924f69917 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences2/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences2/api-extractor-scenarios.api.json @@ -201,16 +201,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "methodA1" }, { @@ -231,16 +231,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "methodA3" } ], @@ -277,16 +277,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "methodB2" }, { @@ -307,16 +307,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "methodB4" } ], @@ -353,16 +353,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "method1" }, { @@ -383,16 +383,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "method2" } ], diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences3/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences3/api-extractor-scenarios.api.json index 53224316fe5..158b62ad07d 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences3/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/docReferences3/api-extractor-scenarios.api.json @@ -201,6 +201,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "myProperty", @@ -256,16 +257,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "myMethod" } ], diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType/api-extractor-scenarios.api.json index 0f5d2794d44..69c76780983 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType/api-extractor-scenarios.api.json @@ -206,6 +206,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "lib1", @@ -239,6 +240,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "lib2", @@ -272,6 +274,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "lib3", @@ -305,6 +308,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "options", @@ -338,6 +342,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "reExport", diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType2/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType2/api-extractor-scenarios.api.json index c7bdadc9c91..f958f2f2214 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType2/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType2/api-extractor-scenarios.api.json @@ -210,6 +210,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "dottedImportType", @@ -245,6 +246,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "dottedImportType2", diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType3/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType3/api-extractor-scenarios.api.json index d6b16a75d00..e2e5296d90c 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType3/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/dynamicImportType3/api-extractor-scenarios.api.json @@ -219,6 +219,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "generic", diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/excerptTokens/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/excerptTokens/api-extractor-scenarios.api.json index 3f533ee760a..c145dad23bb 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/excerptTokens/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/excerptTokens/api-extractor-scenarios.api.json @@ -184,6 +184,7 @@ "text": "number" } ], + "isReadonly": false, "releaseTag": "Public", "name": "MY_CONSTANT", "variableTypeTokenRange": { @@ -238,14 +239,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 5, "endIndex": 6 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [ { @@ -265,6 +265,7 @@ "isOptional": false } ], + "isOptional": false, "name": "someMethod" } ], diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/exportEquals/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/exportEquals/api-extractor-scenarios.api.json index e472b4d3470..fbc45d3d9ab 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/exportEquals/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/exportEquals/api-extractor-scenarios.api.json @@ -202,6 +202,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "context", diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/exportImportStarAs/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/exportImportStarAs/api-extractor-scenarios.api.json index 677c43c0503..96cc42782c0 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/exportImportStarAs/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/exportImportStarAs/api-extractor-scenarios.api.json @@ -252,6 +252,7 @@ "text": "string" } ], + "isReadonly": true, "releaseTag": "Public", "name": "calucatorVersion", "variableTypeTokenRange": { @@ -403,6 +404,7 @@ "text": "string" } ], + "isReadonly": true, "releaseTag": "Public", "name": "calucatorVersion", "variableTypeTokenRange": { diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/functionOverload/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/functionOverload/api-extractor-scenarios.api.json index 724f1432540..78b861a36dd 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/functionOverload/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/functionOverload/api-extractor-scenarios.api.json @@ -397,14 +397,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 5, "endIndex": 6 }, "releaseTag": "Beta", + "isProtected": false, "overloadIndex": 2, "parameters": [ { @@ -424,6 +423,7 @@ "isOptional": false } ], + "isOptional": false, "name": "combine" }, { @@ -460,14 +460,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 5, "endIndex": 6 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 3, "parameters": [ { @@ -487,6 +486,7 @@ "isOptional": false } ], + "isOptional": false, "name": "combine" } ], diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/inconsistentReleaseTags/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/inconsistentReleaseTags/api-extractor-scenarios.api.json index 049b1aa274e..e6a1dd9f430 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/inconsistentReleaseTags/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/inconsistentReleaseTags/api-extractor-scenarios.api.json @@ -201,6 +201,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Beta", "name": "x", diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/internationalCharacters/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/internationalCharacters/api-extractor-scenarios.api.json index f51e8847ea1..e04bf18af4a 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/internationalCharacters/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/internationalCharacters/api-extractor-scenarios.api.json @@ -210,16 +210,6 @@ "text": ";" } ], - "isOptional": false, - "isStatic": false, - "isProtected": false, - "returnTypeTokenRange": { - "startIndex": 3, - "endIndex": 4 - }, - "releaseTag": "Public", - "overloadIndex": 1, - "parameters": [], "typeParameters": [ { "typeParameterName": "T", @@ -233,6 +223,16 @@ } } ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, "name": "\"invalid chars\"" }, { @@ -262,14 +262,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 3, "endIndex": 4 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [ { @@ -281,6 +280,7 @@ "isOptional": false } ], + "isOptional": false, "name": "memberĪ”" }, { @@ -301,16 +301,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "validChars" } ], diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/namedDefaultImport/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/namedDefaultImport/api-extractor-scenarios.api.json index 29b8e929d81..e5fc5f7052d 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/namedDefaultImport/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/namedDefaultImport/api-extractor-scenarios.api.json @@ -206,6 +206,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "dynamicImport", @@ -233,6 +234,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "namedImport", @@ -260,6 +262,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "reExport", diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/readonlyDeclarations/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/readonlyDeclarations/api-extractor-scenarios.api.json new file mode 100644 index 00000000000..c2084c4a386 --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/readonlyDeclarations/api-extractor-scenarios.api.json @@ -0,0 +1,386 @@ +{ + "metadata": { + "toolPackage": "@microsoft/api-extractor", + "toolVersion": "[test mode]", + "schemaVersion": 1006, + "oldestForwardsCompatibleVersion": 1001, + "tsdocConfig": { + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "noStandardTags": true, + "tagDefinitions": [ + { + "tagName": "@alpha", + "syntaxKind": "modifier" + }, + { + "tagName": "@beta", + "syntaxKind": "modifier" + }, + { + "tagName": "@defaultValue", + "syntaxKind": "block" + }, + { + "tagName": "@decorator", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@deprecated", + "syntaxKind": "block" + }, + { + "tagName": "@eventProperty", + "syntaxKind": "modifier" + }, + { + "tagName": "@example", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@experimental", + "syntaxKind": "modifier" + }, + { + "tagName": "@inheritDoc", + "syntaxKind": "inline" + }, + { + "tagName": "@internal", + "syntaxKind": "modifier" + }, + { + "tagName": "@label", + "syntaxKind": "inline" + }, + { + "tagName": "@link", + "syntaxKind": "inline", + "allowMultiple": true + }, + { + "tagName": "@override", + "syntaxKind": "modifier" + }, + { + "tagName": "@packageDocumentation", + "syntaxKind": "modifier" + }, + { + "tagName": "@param", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@privateRemarks", + "syntaxKind": "block" + }, + { + "tagName": "@public", + "syntaxKind": "modifier" + }, + { + "tagName": "@readonly", + "syntaxKind": "modifier" + }, + { + "tagName": "@remarks", + "syntaxKind": "block" + }, + { + "tagName": "@returns", + "syntaxKind": "block" + }, + { + "tagName": "@sealed", + "syntaxKind": "modifier" + }, + { + "tagName": "@see", + "syntaxKind": "block" + }, + { + "tagName": "@throws", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@typeParam", + "syntaxKind": "block", + "allowMultiple": true + }, + { + "tagName": "@virtual", + "syntaxKind": "modifier" + }, + { + "tagName": "@betaDocumentation", + "syntaxKind": "modifier" + }, + { + "tagName": "@internalRemarks", + "syntaxKind": "block" + }, + { + "tagName": "@preapproved", + "syntaxKind": "modifier" + } + ], + "supportForTags": { + "@alpha": true, + "@beta": true, + "@defaultValue": true, + "@decorator": true, + "@deprecated": true, + "@eventProperty": true, + "@example": true, + "@experimental": true, + "@inheritDoc": true, + "@internal": true, + "@label": true, + "@link": true, + "@override": true, + "@packageDocumentation": true, + "@param": true, + "@privateRemarks": true, + "@public": true, + "@readonly": true, + "@remarks": true, + "@returns": true, + "@sealed": true, + "@see": true, + "@throws": true, + "@typeParam": true, + "@virtual": true, + "@betaDocumentation": true, + "@internalRemarks": true, + "@preapproved": true + }, + "reportUnsupportedHtmlElements": false + } + }, + "kind": "Package", + "canonicalReference": "api-extractor-scenarios!", + "docComment": "", + "name": "api-extractor-scenarios", + "members": [ + { + "kind": "EntryPoint", + "canonicalReference": "api-extractor-scenarios!", + "name": "", + "members": [ + { + "kind": "Interface", + "canonicalReference": "api-extractor-scenarios!_IInternalThing:interface", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export interface _IInternalThing " + } + ], + "releaseTag": "Public", + "name": "_IInternalThing", + "members": [ + { + "kind": "PropertySignature", + "canonicalReference": "api-extractor-scenarios!_IInternalThing#title:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "title: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "title", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + } + ], + "extendsTokenRanges": [] + }, + { + "kind": "Variable", + "canonicalReference": "api-extractor-scenarios!FOO:var", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "FOO = \"foo\"" + } + ], + "isReadonly": true, + "releaseTag": "Public", + "name": "FOO", + "variableTypeTokenRange": { + "startIndex": 0, + "endIndex": 0 + } + }, + { + "kind": "Class", + "canonicalReference": "api-extractor-scenarios!MyClass:class", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare class MyClass " + } + ], + "releaseTag": "Public", + "name": "MyClass", + "members": [ + { + "kind": "Property", + "canonicalReference": "api-extractor-scenarios!MyClass#_onlyHasGetterThing:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "get _onlyHasGetterThing(): " + }, + { + "kind": "Reference", + "text": "_IInternalThing", + "canonicalReference": "api-extractor-scenarios!_IInternalThing:interface" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": true, + "isOptional": false, + "releaseTag": "Public", + "name": "_onlyHasGetterThing", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false + }, + { + "kind": "Property", + "canonicalReference": "api-extractor-scenarios!MyClass#_writableThing:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "get _writableThing(): " + }, + { + "kind": "Reference", + "text": "_IInternalThing", + "canonicalReference": "api-extractor-scenarios!_IInternalThing:interface" + }, + { + "kind": "Content", + "text": ";" + }, + { + "kind": "Content", + "text": "\n\nset _writableThing(value: " + }, + { + "kind": "Reference", + "text": "_IInternalThing", + "canonicalReference": "api-extractor-scenarios!_IInternalThing:interface" + }, + { + "kind": "Content", + "text": ");" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "_writableThing", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false + }, + { + "kind": "Property", + "canonicalReference": "api-extractor-scenarios!MyClass.declaredReadonlyThing:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "static readonly declaredReadonlyThing: " + }, + { + "kind": "Reference", + "text": "_IInternalThing", + "canonicalReference": "api-extractor-scenarios!_IInternalThing:interface" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": true, + "isOptional": false, + "releaseTag": "Public", + "name": "declaredReadonlyThing", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": true, + "isProtected": false + }, + { + "kind": "Property", + "canonicalReference": "api-extractor-scenarios!MyClass#tsDocReadonlyThing:member", + "docComment": "/**\n * Technically isn't but for testing purposes\n *\n * @readonly\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "tsDocReadonlyThing: " + }, + { + "kind": "Reference", + "text": "_IInternalThing", + "canonicalReference": "api-extractor-scenarios!_IInternalThing:interface" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": true, + "isOptional": false, + "releaseTag": "Public", + "name": "tsDocReadonlyThing", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false + } + ], + "implementsTokenRanges": [] + } + ] + } + ] +} diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/readonlyDeclarations/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/test-outputs/readonlyDeclarations/api-extractor-scenarios.api.md new file mode 100644 index 00000000000..a3b886324d9 --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/readonlyDeclarations/api-extractor-scenarios.api.md @@ -0,0 +1,30 @@ +## API Report File for "api-extractor-scenarios" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +// @public (undocumented) +export const FOO = "foo"; + +// @public (undocumented) +export interface _IInternalThing { + // (undocumented) + title: string; +} + +// @public (undocumented) +export class MyClass { + // (undocumented) + static readonly declaredReadonlyThing: _IInternalThing; + // (undocumented) + get _onlyHasGetterThing(): _IInternalThing; + tsDocReadonlyThing: _IInternalThing; + // (undocumented) + get _writableThing(): _IInternalThing; + set _writableThing(value: _IInternalThing); +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/readonlyDeclarations/rollup.d.ts b/build-tests/api-extractor-scenarios/etc/test-outputs/readonlyDeclarations/rollup.d.ts new file mode 100644 index 00000000000..d133028f380 --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/readonlyDeclarations/rollup.d.ts @@ -0,0 +1,22 @@ +/** @public */ +export declare const FOO = "foo"; + +/** @public */ +export declare interface _IInternalThing { + title: string; +} + +/** @public */ +export declare class MyClass { + get _writableThing(): _IInternalThing; + set _writableThing(value: _IInternalThing); + get _onlyHasGetterThing(): _IInternalThing; + static readonly declaredReadonlyThing: _IInternalThing; + /** + * Technically isn't but for testing purposes + * @readonly + */ + tsDocReadonlyThing: _IInternalThing; +} + +export { } diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/spanSorting/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/spanSorting/api-extractor-scenarios.api.json index 6fbf248aaa0..d5cafe486ab 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/spanSorting/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/spanSorting/api-extractor-scenarios.api.json @@ -201,6 +201,7 @@ "text": ";" } ], + "isReadonly": false, "isOptional": false, "releaseTag": "Public", "name": "member1", @@ -234,16 +235,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 3 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "member2" } ], @@ -288,14 +289,13 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 3, "endIndex": 4 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [ { @@ -307,6 +307,7 @@ "isOptional": false } ], + "isOptional": false, "name": "tryLoadFromFile" } ], @@ -343,16 +344,16 @@ "text": ";" } ], - "isOptional": false, "isStatic": false, - "isProtected": false, "returnTypeTokenRange": { "startIndex": 1, "endIndex": 2 }, "releaseTag": "Public", + "isProtected": false, "overloadIndex": 1, "parameters": [], + "isOptional": false, "name": "member1" } ], @@ -372,6 +373,7 @@ "text": "(o: {\n a: number;\n b(): string;\n}) => void" } ], + "isReadonly": true, "releaseTag": "Public", "name": "exampleD", "variableTypeTokenRange": { diff --git a/build-tests/api-extractor-scenarios/etc/test-outputs/typeParameters/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/test-outputs/typeParameters/api-extractor-scenarios.api.json index 50102ff1a5f..74e92fe01a7 100644 --- a/build-tests/api-extractor-scenarios/etc/test-outputs/typeParameters/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/test-outputs/typeParameters/api-extractor-scenarios.api.json @@ -201,16 +201,6 @@ "text": ";" } ], - "isOptional": false, - "isStatic": false, - "isProtected": false, - "returnTypeTokenRange": { - "startIndex": 1, - "endIndex": 2 - }, - "releaseTag": "Public", - "overloadIndex": 1, - "parameters": [], "typeParameters": [ { "typeParameterName": "T", @@ -224,6 +214,16 @@ } } ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, "name": "method" } ], diff --git a/build-tests/api-extractor-scenarios/src/readonlyDeclarations/index.ts b/build-tests/api-extractor-scenarios/src/readonlyDeclarations/index.ts new file mode 100644 index 00000000000..9d627cc9b4c --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/readonlyDeclarations/index.ts @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** @public */ +export interface _IInternalThing { + title: string; +} + +/** @public */ +export const FOO = 'foo'; + +/** @public */ +export class MyClass { + public get _writableThing(): _IInternalThing { + return { title: 'thing' }; + } + + public set _writableThing(value: _IInternalThing) {} + + public get _onlyHasGetterThing(): _IInternalThing { + return { title: 'thing' }; + } + + static readonly declaredReadonlyThing: _IInternalThing; + + /** + * Technically isn't but for testing purposes + * @readonly + */ + public tsDocReadonlyThing: _IInternalThing; +} diff --git a/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml b/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml index 5291a3421c7..9fab90318d7 100644 --- a/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml +++ b/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml @@ -5,13 +5,13 @@ importers: typescript-newest-test: specifiers: '@rushstack/eslint-config': file:rushstack-eslint-config-2.6.0.tgz - '@rushstack/heft': file:rushstack-heft-0.45.4.tgz + '@rushstack/heft': file:rushstack-heft-0.45.5.tgz eslint: ~8.7.0 tslint: ~5.20.1 typescript: ~4.6.3 devDependencies: '@rushstack/eslint-config': file:../temp/tarballs/rushstack-eslint-config-2.6.0.tgz_eslint@8.7.0+typescript@4.6.3 - '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.45.4.tgz + '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.45.5.tgz eslint: 8.7.0 tslint: 5.20.1_typescript@4.6.3 typescript: 4.6.3 @@ -19,13 +19,13 @@ importers: typescript-v3-test: specifiers: '@rushstack/eslint-config': file:rushstack-eslint-config-2.6.0.tgz - '@rushstack/heft': file:rushstack-heft-0.45.4.tgz + '@rushstack/heft': file:rushstack-heft-0.45.5.tgz eslint: ~8.7.0 tslint: ~5.20.1 typescript: ~4.6.3 devDependencies: '@rushstack/eslint-config': file:../temp/tarballs/rushstack-eslint-config-2.6.0.tgz_eslint@8.7.0+typescript@4.6.3 - '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.45.4.tgz + '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.45.5.tgz eslint: 8.7.0 tslint: 5.20.1_typescript@4.6.3 typescript: 4.6.3 @@ -831,7 +831,7 @@ packages: dev: true /glob-escape/0.0.2: - resolution: {integrity: sha1-nCf3gh7RwTd1gvPv2VWOP2dWKO0=} + resolution: {integrity: sha512-L/cXYz8x7qer1HAyUQ+mbjcUsJVdpRxpAf7CwqHoNBs9vTpABlGfNN4tzkDxt+u3Z7ZncVyKlCNPtzb0R/7WbA==} engines: {node: '>= 0.10'} dev: true @@ -850,7 +850,7 @@ packages: dev: true /glob/7.0.6: - resolution: {integrity: sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=} + resolution: {integrity: sha512-f8c0rE8JiCxpa52kWPAOa3ZaYEnzofDzCQLCn3Vdk0Z5OVLq3BsRFJI4S4ykpeVW6QMGBUkMeUpoEgWnMTnw5Q==} dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -1073,7 +1073,7 @@ packages: dev: true /jju/1.4.0: - resolution: {integrity: sha1-o6vicYryQaKykE+EpiWXDzia4yo=} + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} dev: true /js-tokens/4.0.0: @@ -1104,7 +1104,7 @@ packages: dev: true /jsonfile/4.0.0: - resolution: {integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=} + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: graceful-fs: 4.2.6 dev: true @@ -1131,11 +1131,11 @@ packages: dev: true /lodash.get/4.4.2: - resolution: {integrity: sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=} + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} dev: true /lodash.isequal/4.5.0: - resolution: {integrity: sha1-QVxEePK8wwEgwizhDtMib30+GOA=} + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} dev: true /lodash.merge/4.6.2: @@ -1200,7 +1200,7 @@ packages: dev: true /object-assign/4.1.1: - resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} dev: true @@ -1258,7 +1258,7 @@ packages: dev: true /once/1.4.0: - resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 dev: true @@ -1753,10 +1753,10 @@ packages: - typescript dev: true - file:../temp/tarballs/rushstack-heft-0.45.4.tgz: - resolution: {tarball: file:../temp/tarballs/rushstack-heft-0.45.4.tgz} + file:../temp/tarballs/rushstack-heft-0.45.5.tgz: + resolution: {tarball: file:../temp/tarballs/rushstack-heft-0.45.5.tgz} name: '@rushstack/heft' - version: 0.45.4 + version: 0.45.5 engines: {node: '>=10.13.0'} hasBin: true dependencies: diff --git a/common/changes/@microsoft/api-extractor-model/api-readonly-mixin_2022-05-19-20-18.json b/common/changes/@microsoft/api-extractor-model/api-readonly-mixin_2022-05-19-20-18.json new file mode 100644 index 00000000000..b0380433357 --- /dev/null +++ b/common/changes/@microsoft/api-extractor-model/api-readonly-mixin_2022-05-19-20-18.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/api-extractor-model", + "comment": "Add an \"isReadonly\" field to ApiProperty, ApiPropertySignature, and ApiVariable", + "type": "minor" + } + ], + "packageName": "@microsoft/api-extractor-model" +} \ No newline at end of file diff --git a/common/changes/@microsoft/api-extractor-model/protected-mixin_2022-05-20-22-50.json b/common/changes/@microsoft/api-extractor-model/protected-mixin_2022-05-20-22-50.json index dec7b0ee3ed..8d4390c4dbd 100644 --- a/common/changes/@microsoft/api-extractor-model/protected-mixin_2022-05-20-22-50.json +++ b/common/changes/@microsoft/api-extractor-model/protected-mixin_2022-05-20-22-50.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@microsoft/api-extractor-model", - "comment": "Add parsing logic to parse whether a class constructor, property, or method has the 'protected' modifier.", + "comment": "Add an \"isProtected\" field to ApiConstructor, ApiMethod, and ApiProperty", "type": "minor" } ], diff --git a/common/changes/@microsoft/api-extractor/api-readonly-mixin_2022-05-19-20-18.json b/common/changes/@microsoft/api-extractor/api-readonly-mixin_2022-05-19-20-18.json new file mode 100644 index 00000000000..000ad71a5d1 --- /dev/null +++ b/common/changes/@microsoft/api-extractor/api-readonly-mixin_2022-05-19-20-18.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/api-extractor", + "comment": "Add an \"isReadonly\" field to the doc model to indicate whether a property or variable is readonly", + "type": "minor" + } + ], + "packageName": "@microsoft/api-extractor" +} \ No newline at end of file diff --git a/common/changes/@microsoft/api-extractor/protected-mixin_2022-05-20-22-50.json b/common/changes/@microsoft/api-extractor/protected-mixin_2022-05-20-22-50.json index bf444adf5ac..1099b359645 100644 --- a/common/changes/@microsoft/api-extractor/protected-mixin_2022-05-20-22-50.json +++ b/common/changes/@microsoft/api-extractor/protected-mixin_2022-05-20-22-50.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@microsoft/api-extractor", - "comment": "Add an ApiParameterMixin that adds an isProtected parameter to class constructor, property, and method API items.", + "comment": "Add an \"isProtected\" field to the doc model to indicate protected class members", "type": "minor" } ], diff --git a/common/reviews/api/api-extractor-model.api.md b/common/reviews/api/api-extractor-model.api.md index 51e591204da..3e248e4d580 100644 --- a/common/reviews/api/api-extractor-model.api.md +++ b/common/reviews/api/api-extractor-model.api.md @@ -522,6 +522,21 @@ export namespace ApiProtectedMixin { export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiProtectedMixin; } +// @public +export function ApiReadonlyMixin(baseClass: TBaseClass): TBaseClass & (new (...args: any[]) => ApiReadonlyMixin); + +// @public +export interface ApiReadonlyMixin extends ApiItem { + readonly isReadonly: boolean; + // (undocumented) + serializeInto(jsonObject: Partial): void; +} + +// @public +export namespace ApiReadonlyMixin { + export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiReadonlyMixin; +} + // @public export function ApiReleaseTagMixin(baseClass: TBaseClass): TBaseClass & (new (...args: any[]) => ApiReleaseTagMixin); @@ -736,7 +751,7 @@ export interface IApiItemOptions { } // @public -export interface IApiMethodOptions extends IApiNameMixinOptions, IApiTypeParameterListMixinOptions, IApiParameterListMixinOptions, IApiProtectedMixinOptions, IApiReleaseTagMixinOptions, IApiReturnTypeMixinOptions, IApiStaticMixinOptions, IApiOptionalMixinOptions, IApiDeclaredItemOptions { +export interface IApiMethodOptions extends IApiNameMixinOptions, IApiOptionalMixinOptions, IApiParameterListMixinOptions, IApiProtectedMixinOptions, IApiReleaseTagMixinOptions, IApiReturnTypeMixinOptions, IApiStaticMixinOptions, IApiTypeParameterListMixinOptions, IApiDeclaredItemOptions { } // @public (undocumented) @@ -791,7 +806,7 @@ export interface IApiParameterOptions { } // @public -export interface IApiPropertyItemOptions extends IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiOptionalMixinOptions, IApiDeclaredItemOptions { +export interface IApiPropertyItemOptions extends IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiOptionalMixinOptions, IApiReadonlyMixinOptions, IApiDeclaredItemOptions { // (undocumented) propertyTypeTokenRange: IExcerptTokenRange; } @@ -810,6 +825,12 @@ export interface IApiProtectedMixinOptions extends IApiItemOptions { isProtected: boolean; } +// @public +export interface IApiReadonlyMixinOptions extends IApiItemOptions { + // (undocumented) + isReadonly: boolean; +} + // @public export interface IApiReleaseTagMixinOptions extends IApiItemOptions { // (undocumented) @@ -851,7 +872,7 @@ export interface IApiTypeParameterOptions { } // @public -export interface IApiVariableOptions extends IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions { +export interface IApiVariableOptions extends IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiReadonlyMixinOptions, IApiDeclaredItemOptions { // (undocumented) variableTypeTokenRange: IExcerptTokenRange; } diff --git a/libraries/api-extractor-model/src/index.ts b/libraries/api-extractor-model/src/index.ts index f3c7c610922..41e9d88d1e6 100644 --- a/libraries/api-extractor-model/src/index.ts +++ b/libraries/api-extractor-model/src/index.ts @@ -37,6 +37,7 @@ export { IApiReturnTypeMixinOptions, ApiReturnTypeMixin } from './mixins/ApiRetu export { IApiStaticMixinOptions, ApiStaticMixin } from './mixins/ApiStaticMixin'; export { IApiNameMixinOptions, ApiNameMixin } from './mixins/ApiNameMixin'; export { IApiOptionalMixinOptions, ApiOptionalMixin } from './mixins/ApiOptionalMixin'; +export { IApiReadonlyMixinOptions, ApiReadonlyMixin } from './mixins/ApiReadonlyMixin'; export { ExcerptTokenKind, IExcerptTokenRange, IExcerptToken, ExcerptToken, Excerpt } from './mixins/Excerpt'; export { Constructor, PropertiesOf } from './mixins/Mixin'; diff --git a/libraries/api-extractor-model/src/items/ApiPropertyItem.ts b/libraries/api-extractor-model/src/items/ApiPropertyItem.ts index 9290f5deff4..9a9cfefd650 100644 --- a/libraries/api-extractor-model/src/items/ApiPropertyItem.ts +++ b/libraries/api-extractor-model/src/items/ApiPropertyItem.ts @@ -7,6 +7,7 @@ import { ApiReleaseTagMixin, IApiReleaseTagMixinOptions } from '../mixins/ApiRel import { IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin'; import { DeserializerContext } from '../model/DeserializerContext'; import { ApiOptionalMixin, IApiOptionalMixinOptions } from '../mixins/ApiOptionalMixin'; +import { ApiReadonlyMixin, IApiReadonlyMixinOptions } from '../mixins/ApiReadonlyMixin'; /** * Constructor options for {@link ApiPropertyItem}. @@ -16,6 +17,7 @@ export interface IApiPropertyItemOptions extends IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiOptionalMixinOptions, + IApiReadonlyMixinOptions, IApiDeclaredItemOptions { propertyTypeTokenRange: IExcerptTokenRange; } @@ -29,7 +31,9 @@ export interface IApiPropertyItemJson extends IApiDeclaredItemJson { * * @public */ -export class ApiPropertyItem extends ApiNameMixin(ApiReleaseTagMixin(ApiOptionalMixin(ApiDeclaredItem))) { +export class ApiPropertyItem extends ApiNameMixin( + ApiReleaseTagMixin(ApiOptionalMixin(ApiReadonlyMixin(ApiDeclaredItem))) +) { /** * An {@link Excerpt} that describes the type of the property. */ diff --git a/libraries/api-extractor-model/src/mixins/ApiReadonlyMixin.ts b/libraries/api-extractor-model/src/mixins/ApiReadonlyMixin.ts new file mode 100644 index 00000000000..45de9a603bd --- /dev/null +++ b/libraries/api-extractor-model/src/mixins/ApiReadonlyMixin.ts @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information.s + +import { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem'; +import { DeserializerContext } from '../model/DeserializerContext'; + +/** + * Constructor options for {@link (ApiReadonlyMixin:interface)}. + * @public + */ +export interface IApiReadonlyMixinOptions extends IApiItemOptions { + isReadonly: boolean; +} + +export interface IApiReadonlyMixinJson extends IApiItemJson { + isReadonly: boolean; +} + +const _isReadonly: unique symbol = Symbol('ApiReadonlyMixin._isReadonly'); + +/** + * The mixin base class for API items that cannot be modified after instantiation. + * Examples such as the readonly modifier and only having a getter but no setter. + * + * @remarks + * + * This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of + * API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use + * TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various + * features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class + * to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components: + * the function that generates a subclass, an interface that describes the members of the subclass, and + * a namespace containing static members of the class. + * + * @public + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export interface ApiReadonlyMixin extends ApiItem { + /** + * Indicates that the API item's value cannot be assigned by an external consumer. + * + * @remarks + * Examples of API items that would be considered "read only" by API Extractor: + * + * - A class or interface's property that has the `readonly` modifier. + * + * - A variable that has the `const` modifier. + * + * - A property or variable whose TSDoc comment includes the `@readonly` tag. + * + * - A property declaration with a getter but no setter. + * + * Note that if the `readonly` keyword appears in a type annotation, this does not + * guarantee that that the API item will be considered readonly. For example: + * + * ```ts + * declare class C { + * // isReadonly=false in this case, because C.x is assignable + * public x: readonly string[]; + * } + * ``` + */ + readonly isReadonly: boolean; + + serializeInto(jsonObject: Partial): void; +} + +/** + * Mixin function for {@link (ApiReadonlyMixin:interface)}. + * + * @param baseClass - The base class to be extended + * @returns A child class that extends baseClass, adding the {@link (ApiReadonlyMixin:interface)} + * functionality. + * + * @public + */ +export function ApiReadonlyMixin( + baseClass: TBaseClass + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): TBaseClass & (new (...args: any[]) => ApiReadonlyMixin) { + class MixedClass extends baseClass implements ApiReadonlyMixin { + public [_isReadonly]: boolean; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public constructor(...args: any[]) { + super(...args); + + const options: IApiReadonlyMixinOptions = args[0]; + this[_isReadonly] = options.isReadonly; + } + + /** @override */ + public static onDeserializeInto( + options: Partial, + context: DeserializerContext, + jsonObject: IApiReadonlyMixinJson + ): void { + baseClass.onDeserializeInto(options, context, jsonObject); + + options.isReadonly = jsonObject.isReadonly || false; + } + + public get isReadonly(): boolean { + return this[_isReadonly]; + } + + /** @override */ + public serializeInto(jsonObject: Partial): void { + super.serializeInto(jsonObject); + + jsonObject.isReadonly = this.isReadonly; + } + } + + return MixedClass; +} + +/** + * Static members for {@link (ApiReadonlyMixin:interface)}. + * @public + */ +export namespace ApiReadonlyMixin { + /** + * A type guard that tests whether the specified `ApiItem` subclass extends the `ApiReadonlyMixin` mixin. + * + * @remarks + * + * The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of + * the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however + * the TypeScript type system cannot invoke a runtime test.) + */ + export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiReadonlyMixin { + return apiItem.hasOwnProperty(_isReadonly); + } +} diff --git a/libraries/api-extractor-model/src/model/ApiConstructor.ts b/libraries/api-extractor-model/src/model/ApiConstructor.ts index 51261d157da..c25a7e1e4a5 100644 --- a/libraries/api-extractor-model/src/model/ApiConstructor.ts +++ b/libraries/api-extractor-model/src/model/ApiConstructor.ts @@ -50,7 +50,7 @@ export interface IApiConstructorOptions * @public */ export class ApiConstructor extends ApiParameterListMixin( - ApiReleaseTagMixin(ApiProtectedMixin(ApiDeclaredItem)) + ApiProtectedMixin(ApiReleaseTagMixin(ApiDeclaredItem)) ) { public constructor(options: IApiConstructorOptions) { super(options); diff --git a/libraries/api-extractor-model/src/model/ApiMethod.ts b/libraries/api-extractor-model/src/model/ApiMethod.ts index d461ef67b1e..c5b71ae03a5 100644 --- a/libraries/api-extractor-model/src/model/ApiMethod.ts +++ b/libraries/api-extractor-model/src/model/ApiMethod.ts @@ -27,13 +27,13 @@ import { ApiOptionalMixin, IApiOptionalMixinOptions } from '../mixins/ApiOptiona */ export interface IApiMethodOptions extends IApiNameMixinOptions, - IApiTypeParameterListMixinOptions, + IApiOptionalMixinOptions, IApiParameterListMixinOptions, IApiProtectedMixinOptions, IApiReleaseTagMixinOptions, IApiReturnTypeMixinOptions, IApiStaticMixinOptions, - IApiOptionalMixinOptions, + IApiTypeParameterListMixinOptions, IApiDeclaredItemOptions {} /** @@ -58,10 +58,10 @@ export interface IApiMethodOptions * @public */ export class ApiMethod extends ApiNameMixin( - ApiTypeParameterListMixin( + ApiOptionalMixin( ApiParameterListMixin( - ApiReleaseTagMixin( - ApiReturnTypeMixin(ApiProtectedMixin(ApiStaticMixin(ApiOptionalMixin(ApiDeclaredItem)))) + ApiProtectedMixin( + ApiReleaseTagMixin(ApiReturnTypeMixin(ApiStaticMixin(ApiTypeParameterListMixin(ApiDeclaredItem)))) ) ) ) diff --git a/libraries/api-extractor-model/src/model/ApiVariable.ts b/libraries/api-extractor-model/src/model/ApiVariable.ts index 85cfef5e558..c476693dabb 100644 --- a/libraries/api-extractor-model/src/model/ApiVariable.ts +++ b/libraries/api-extractor-model/src/model/ApiVariable.ts @@ -10,6 +10,7 @@ import { import { ApiItemKind } from '../items/ApiItem'; import { ApiDeclaredItem, IApiDeclaredItemOptions, IApiDeclaredItemJson } from '../items/ApiDeclaredItem'; import { ApiReleaseTagMixin, IApiReleaseTagMixinOptions } from '../mixins/ApiReleaseTagMixin'; +import { ApiReadonlyMixin, IApiReadonlyMixinOptions } from '../mixins/ApiReadonlyMixin'; import { IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin'; import { IExcerptTokenRange, Excerpt } from '../mixins/Excerpt'; import { DeserializerContext } from './DeserializerContext'; @@ -21,6 +22,7 @@ import { DeserializerContext } from './DeserializerContext'; export interface IApiVariableOptions extends IApiNameMixinOptions, IApiReleaseTagMixinOptions, + IApiReadonlyMixinOptions, IApiDeclaredItemOptions { variableTypeTokenRange: IExcerptTokenRange; } @@ -49,7 +51,7 @@ export interface IApiVariableJson extends IApiDeclaredItemJson { * * @public */ -export class ApiVariable extends ApiNameMixin(ApiReleaseTagMixin(ApiDeclaredItem)) { +export class ApiVariable extends ApiNameMixin(ApiReleaseTagMixin(ApiReadonlyMixin(ApiDeclaredItem))) { /** * An {@link Excerpt} that describes the type of the variable. */ diff --git a/libraries/api-extractor-model/src/model/DeserializerContext.ts b/libraries/api-extractor-model/src/model/DeserializerContext.ts index 12cd9e15514..07ea19ce36f 100644 --- a/libraries/api-extractor-model/src/model/DeserializerContext.ts +++ b/libraries/api-extractor-model/src/model/DeserializerContext.ts @@ -36,17 +36,20 @@ export enum ApiJsonSchemaVersion { V_1004 = 1004, /** - * Add an `isOptional` field to `IApiParameterOptions` to track whether a function parameter is optional. + * Add an `isOptional` field to `Parameter` and `TypeParameter` to track whether a function parameter is optional. * * When loading older JSON files, the value defaults to `false`. */ V_1005 = 1005, /** - * Add an `isProtected` field to `IApiConstructorOptions`, `IApiPropertyOptions`, and `IApiMethodOptions` to + * Add an `isProtected` field to `ApiConstructor`, `ApiMethod`, and `ApiProperty` to * track whether a class member has the `protected` modifier. * - * When loading older JSON files, the value defaults to `false`. + * Add an `isReadonly` field to `ApiProperty`, `ApiPropertySignature`, and `ApiVariable` to + * track whether the item is readonly. + * + * When loading older JSON files, the values default to `false`. */ V_1006 = 1006,