UNPKG

37.1 kBJavaScriptView Raw
1"use strict";
2// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
3// See LICENSE in the project root for license information.
4var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5 if (k2 === undefined) k2 = k;
6 var desc = Object.getOwnPropertyDescriptor(m, k);
7 if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8 desc = { enumerable: true, get: function() { return m[k]; } };
9 }
10 Object.defineProperty(o, k2, desc);
11}) : (function(o, m, k, k2) {
12 if (k2 === undefined) k2 = k;
13 o[k2] = m[k];
14}));
15var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16 Object.defineProperty(o, "default", { enumerable: true, value: v });
17}) : function(o, v) {
18 o["default"] = v;
19});
20var __importStar = (this && this.__importStar) || function (mod) {
21 if (mod && mod.__esModule) return mod;
22 var result = {};
23 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
24 __setModuleDefault(result, mod);
25 return result;
26};
27Object.defineProperty(exports, "__esModule", { value: true });
28exports.Collector = void 0;
29const ts = __importStar(require("typescript"));
30const tsdoc = __importStar(require("@microsoft/tsdoc"));
31const node_core_library_1 = require("@rushstack/node-core-library");
32const api_extractor_model_1 = require("@microsoft/api-extractor-model");
33const CollectorEntity_1 = require("./CollectorEntity");
34const AstSymbolTable_1 = require("../analyzer/AstSymbolTable");
35const AstSymbol_1 = require("../analyzer/AstSymbol");
36const TypeScriptHelpers_1 = require("../analyzer/TypeScriptHelpers");
37const WorkingPackage_1 = require("./WorkingPackage");
38const PackageDocComment_1 = require("../aedoc/PackageDocComment");
39const DeclarationMetadata_1 = require("./DeclarationMetadata");
40const ApiItemMetadata_1 = require("./ApiItemMetadata");
41const SymbolMetadata_1 = require("./SymbolMetadata");
42const TypeScriptInternals_1 = require("../analyzer/TypeScriptInternals");
43const AstReferenceResolver_1 = require("../analyzer/AstReferenceResolver");
44const ExtractorConfig_1 = require("../api/ExtractorConfig");
45const AstNamespaceImport_1 = require("../analyzer/AstNamespaceImport");
46const AstImport_1 = require("../analyzer/AstImport");
47/**
48 * The `Collector` manages the overall data set that is used by `ApiModelGenerator`,
49 * `DtsRollupGenerator`, and `ApiReportGenerator`. Starting from the working package's entry point,
50 * the `Collector` collects all exported symbols, determines how to import any symbols they reference,
51 * assigns unique names, and sorts everything into a normalized alphabetical ordering.
52 */
53class Collector {
54 constructor(options) {
55 this._entities = [];
56 this._entitiesByAstEntity = new Map();
57 this._starExportedExternalModulePaths = [];
58 this._dtsTypeReferenceDirectives = new Set();
59 this._dtsLibReferenceDirectives = new Set();
60 this.packageJsonLookup = new node_core_library_1.PackageJsonLookup();
61 this._program = options.program;
62 this.extractorConfig = options.extractorConfig;
63 const entryPointSourceFile = options.program.getSourceFile(this.extractorConfig.mainEntryPointFilePath);
64 if (!entryPointSourceFile) {
65 throw new Error('Unable to load file: ' + this.extractorConfig.mainEntryPointFilePath);
66 }
67 if (!this.extractorConfig.packageFolder || !this.extractorConfig.packageJson) {
68 // TODO: We should be able to analyze projects that don't have any package.json.
69 // The ExtractorConfig class is already designed to allow this.
70 throw new Error('Unable to find a package.json file for the project being analyzed');
71 }
72 this.workingPackage = new WorkingPackage_1.WorkingPackage({
73 packageFolder: this.extractorConfig.packageFolder,
74 packageJson: this.extractorConfig.packageJson,
75 entryPointSourceFile
76 });
77 this.messageRouter = options.messageRouter;
78 this.program = options.program;
79 this.typeChecker = options.program.getTypeChecker();
80 this.globalVariableAnalyzer = TypeScriptInternals_1.TypeScriptInternals.getGlobalVariableAnalyzer(this.program);
81 this._tsdocParser = new tsdoc.TSDocParser(this.extractorConfig.tsdocConfiguration);
82 this.bundledPackageNames = new Set(this.extractorConfig.bundledPackages);
83 this.astSymbolTable = new AstSymbolTable_1.AstSymbolTable(this.program, this.typeChecker, this.packageJsonLookup, this.bundledPackageNames, this.messageRouter);
84 this.astReferenceResolver = new AstReferenceResolver_1.AstReferenceResolver(this);
85 this._cachedOverloadIndexesByDeclaration = new Map();
86 }
87 /**
88 * Returns a list of names (e.g. "example-library") that should appear in a reference like this:
89 *
90 * ```
91 * /// <reference types="example-library" />
92 * ```
93 */
94 get dtsTypeReferenceDirectives() {
95 return this._dtsTypeReferenceDirectives;
96 }
97 /**
98 * A list of names (e.g. "runtime-library") that should appear in a reference like this:
99 *
100 * ```
101 * /// <reference lib="runtime-library" />
102 * ```
103 */
104 get dtsLibReferenceDirectives() {
105 return this._dtsLibReferenceDirectives;
106 }
107 get entities() {
108 return this._entities;
109 }
110 /**
111 * A list of module specifiers (e.g. `"@rushstack/node-core-library/lib/FileSystem"`) that should be emitted
112 * as star exports (e.g. `export * from "@rushstack/node-core-library/lib/FileSystem"`).
113 */
114 get starExportedExternalModulePaths() {
115 return this._starExportedExternalModulePaths;
116 }
117 /**
118 * Perform the analysis.
119 */
120 analyze() {
121 if (this._astEntryPoint) {
122 throw new Error('DtsRollupGenerator.analyze() was already called');
123 }
124 // This runs a full type analysis, and then augments the Abstract Syntax Tree (i.e. declarations)
125 // with semantic information (i.e. symbols). The "diagnostics" are a subset of the everyday
126 // compile errors that would result from a full compilation.
127 for (const diagnostic of this._program.getSemanticDiagnostics()) {
128 this.messageRouter.addCompilerDiagnostic(diagnostic);
129 }
130 const sourceFiles = this.program.getSourceFiles();
131 if (this.messageRouter.showDiagnostics) {
132 this.messageRouter.logDiagnosticHeader('Root filenames');
133 for (const fileName of this.program.getRootFileNames()) {
134 this.messageRouter.logDiagnostic(fileName);
135 }
136 this.messageRouter.logDiagnosticFooter();
137 this.messageRouter.logDiagnosticHeader('Files analyzed by compiler');
138 for (const sourceFile of sourceFiles) {
139 this.messageRouter.logDiagnostic(sourceFile.fileName);
140 }
141 this.messageRouter.logDiagnosticFooter();
142 }
143 // We can throw this error earlier in CompilerState.ts, but intentionally wait until after we've logged the
144 // associated diagnostic message above to make debugging easier for developers.
145 // Typically there will be many such files -- to avoid too much noise, only report the first one.
146 const badSourceFile = sourceFiles.find(({ fileName }) => !ExtractorConfig_1.ExtractorConfig.hasDtsFileExtension(fileName));
147 if (badSourceFile) {
148 this.messageRouter.addAnalyzerIssueForPosition("ae-wrong-input-file-type" /* WrongInputFileType */, 'Incorrect file type; API Extractor expects to analyze compiler outputs with the .d.ts file extension. ' +
149 'Troubleshooting tips: https://api-extractor.com/link/dts-error', badSourceFile, 0);
150 }
151 // Build the entry point
152 const entryPointSourceFile = this.workingPackage.entryPointSourceFile;
153 const astEntryPoint = this.astSymbolTable.fetchAstModuleFromWorkingPackage(entryPointSourceFile);
154 this._astEntryPoint = astEntryPoint;
155 const packageDocCommentTextRange = PackageDocComment_1.PackageDocComment.tryFindInSourceFile(entryPointSourceFile, this);
156 if (packageDocCommentTextRange) {
157 const range = tsdoc.TextRange.fromStringRange(entryPointSourceFile.text, packageDocCommentTextRange.pos, packageDocCommentTextRange.end);
158 this.workingPackage.tsdocParserContext = this._tsdocParser.parseRange(range);
159 this.messageRouter.addTsdocMessages(this.workingPackage.tsdocParserContext, entryPointSourceFile);
160 this.workingPackage.tsdocComment = this.workingPackage.tsdocParserContext.docComment;
161 }
162 const exportedAstEntities = [];
163 // Create a CollectorEntity for each top-level export
164 const astModuleExportInfo = this.astSymbolTable.fetchAstModuleExportInfo(astEntryPoint);
165 for (const [exportName, astEntity] of astModuleExportInfo.exportedLocalEntities) {
166 this._createCollectorEntity(astEntity, exportName);
167 exportedAstEntities.push(astEntity);
168 }
169 // Create a CollectorEntity for each indirectly referenced export.
170 // Note that we do this *after* the above loop, so that references to exported AstSymbols
171 // are encountered first as exports.
172 const alreadySeenAstSymbols = new Set();
173 for (const exportedAstEntity of exportedAstEntities) {
174 this._createEntityForIndirectReferences(exportedAstEntity, alreadySeenAstSymbols);
175 if (exportedAstEntity instanceof AstSymbol_1.AstSymbol) {
176 this.fetchSymbolMetadata(exportedAstEntity);
177 }
178 }
179 this._makeUniqueNames();
180 for (const starExportedExternalModule of astModuleExportInfo.starExportedExternalModules) {
181 if (starExportedExternalModule.externalModulePath !== undefined) {
182 this._starExportedExternalModulePaths.push(starExportedExternalModule.externalModulePath);
183 }
184 }
185 node_core_library_1.Sort.sortBy(this._entities, (x) => x.getSortKey());
186 node_core_library_1.Sort.sortSet(this._dtsTypeReferenceDirectives);
187 node_core_library_1.Sort.sortSet(this._dtsLibReferenceDirectives);
188 this._starExportedExternalModulePaths.sort();
189 }
190 /**
191 * For a given ts.Identifier that is part of an AstSymbol that we analyzed, return the CollectorEntity that
192 * it refers to. Returns undefined if it doesn't refer to anything interesting.
193 * @remarks
194 * Throws an Error if the ts.Identifier is not part of node tree that was analyzed.
195 */
196 tryGetEntityForNode(identifier) {
197 const astEntity = this.astSymbolTable.tryGetEntityForNode(identifier);
198 if (astEntity) {
199 return this._entitiesByAstEntity.get(astEntity);
200 }
201 return undefined;
202 }
203 /**
204 * Returns the associated `CollectorEntity` for the given `astEntity`, if one was created during analysis.
205 */
206 tryGetCollectorEntity(astEntity) {
207 return this._entitiesByAstEntity.get(astEntity);
208 }
209 fetchSymbolMetadata(astSymbol) {
210 if (astSymbol.symbolMetadata === undefined) {
211 this._fetchSymbolMetadata(astSymbol);
212 }
213 return astSymbol.symbolMetadata;
214 }
215 fetchDeclarationMetadata(astDeclaration) {
216 if (astDeclaration.declarationMetadata === undefined) {
217 // Fetching the SymbolMetadata always constructs the DeclarationMetadata
218 this._fetchSymbolMetadata(astDeclaration.astSymbol);
219 }
220 return astDeclaration.declarationMetadata;
221 }
222 fetchApiItemMetadata(astDeclaration) {
223 if (astDeclaration.apiItemMetadata === undefined) {
224 // Fetching the SymbolMetadata always constructs the ApiItemMetadata
225 this._fetchSymbolMetadata(astDeclaration.astSymbol);
226 }
227 return astDeclaration.apiItemMetadata;
228 }
229 tryFetchMetadataForAstEntity(astEntity) {
230 if (astEntity instanceof AstSymbol_1.AstSymbol) {
231 return this.fetchSymbolMetadata(astEntity);
232 }
233 if (astEntity instanceof AstImport_1.AstImport) {
234 if (astEntity.astSymbol) {
235 return this.fetchSymbolMetadata(astEntity.astSymbol);
236 }
237 }
238 return undefined;
239 }
240 isAncillaryDeclaration(astDeclaration) {
241 const declarationMetadata = this.fetchDeclarationMetadata(astDeclaration);
242 return declarationMetadata.isAncillary;
243 }
244 getNonAncillaryDeclarations(astSymbol) {
245 const result = [];
246 for (const astDeclaration of astSymbol.astDeclarations) {
247 const declarationMetadata = this.fetchDeclarationMetadata(astDeclaration);
248 if (!declarationMetadata.isAncillary) {
249 result.push(astDeclaration);
250 }
251 }
252 return result;
253 }
254 /**
255 * Removes the leading underscore, for example: "_Example" --> "example*Example*_"
256 *
257 * @remarks
258 * This causes internal definitions to sort alphabetically case-insensitive, then case-sensitive, and
259 * initially ignoring the underscore prefix, while still deterministically comparing it.
260 * The star is used as a delimiter because it is not a legal identifier character.
261 */
262 static getSortKeyIgnoringUnderscore(identifier) {
263 if (!identifier)
264 return '';
265 let parts;
266 if (identifier[0] === '_') {
267 const withoutUnderscore = identifier.substr(1);
268 parts = [withoutUnderscore.toLowerCase(), '*', withoutUnderscore, '*', '_'];
269 }
270 else {
271 parts = [identifier.toLowerCase(), '*', identifier];
272 }
273 return parts.join('');
274 }
275 /**
276 * For function-like signatures, this returns the TSDoc "overload index" which can be used to identify
277 * a specific overload.
278 */
279 getOverloadIndex(astDeclaration) {
280 const allDeclarations = astDeclaration.astSymbol.astDeclarations;
281 if (allDeclarations.length === 1) {
282 return 1; // trivial case
283 }
284 let overloadIndex = this._cachedOverloadIndexesByDeclaration.get(astDeclaration);
285 if (overloadIndex === undefined) {
286 // TSDoc index selectors are positive integers counting from 1
287 let nextIndex = 1;
288 for (const other of allDeclarations) {
289 // Filter out other declarations that are not overloads. For example, an overloaded function can also
290 // be a namespace.
291 if (other.declaration.kind === astDeclaration.declaration.kind) {
292 this._cachedOverloadIndexesByDeclaration.set(other, nextIndex);
293 ++nextIndex;
294 }
295 }
296 overloadIndex = this._cachedOverloadIndexesByDeclaration.get(astDeclaration);
297 }
298 if (overloadIndex === undefined) {
299 // This should never happen
300 throw new node_core_library_1.InternalError('Error calculating overload index for declaration');
301 }
302 return overloadIndex;
303 }
304 _createCollectorEntity(astEntity, exportedName) {
305 let entity = this._entitiesByAstEntity.get(astEntity);
306 if (!entity) {
307 entity = new CollectorEntity_1.CollectorEntity(astEntity);
308 this._entitiesByAstEntity.set(astEntity, entity);
309 this._entities.push(entity);
310 this._collectReferenceDirectives(astEntity);
311 }
312 if (exportedName) {
313 entity.addExportName(exportedName);
314 }
315 return entity;
316 }
317 _createEntityForIndirectReferences(astEntity, alreadySeenAstEntities) {
318 if (alreadySeenAstEntities.has(astEntity)) {
319 return;
320 }
321 alreadySeenAstEntities.add(astEntity);
322 if (astEntity instanceof AstSymbol_1.AstSymbol) {
323 astEntity.forEachDeclarationRecursive((astDeclaration) => {
324 for (const referencedAstEntity of astDeclaration.referencedAstEntities) {
325 if (referencedAstEntity instanceof AstSymbol_1.AstSymbol) {
326 // We only create collector entities for root-level symbols.
327 // For example, if a symbols is nested inside a namespace, only the root-level namespace
328 // get a collector entity
329 if (referencedAstEntity.parentAstSymbol === undefined) {
330 this._createCollectorEntity(referencedAstEntity, undefined);
331 }
332 }
333 else {
334 this._createCollectorEntity(referencedAstEntity, undefined);
335 }
336 this._createEntityForIndirectReferences(referencedAstEntity, alreadySeenAstEntities);
337 }
338 });
339 }
340 if (astEntity instanceof AstNamespaceImport_1.AstNamespaceImport) {
341 const astModuleExportInfo = astEntity.fetchAstModuleExportInfo(this);
342 for (const exportedEntity of astModuleExportInfo.exportedLocalEntities.values()) {
343 // Create a CollectorEntity for each top-level export of AstImportInternal entity
344 const entity = this._createCollectorEntity(exportedEntity, undefined);
345 entity.addAstNamespaceImports(astEntity);
346 this._createEntityForIndirectReferences(exportedEntity, alreadySeenAstEntities);
347 }
348 }
349 }
350 /**
351 * Ensures a unique name for each item in the package typings file.
352 */
353 _makeUniqueNames() {
354 // The following examples illustrate the nameForEmit heuristics:
355 //
356 // Example 1:
357 // class X { } <--- nameForEmit should be "A" to simplify things and reduce possibility of conflicts
358 // export { X as A };
359 //
360 // Example 2:
361 // class X { } <--- nameForEmit should be "X" because choosing A or B would be nondeterministic
362 // export { X as A };
363 // export { X as B };
364 //
365 // Example 3:
366 // class X { } <--- nameForEmit should be "X_1" because Y has a stronger claim to the name
367 // export { X as A };
368 // export { X as B };
369 // class Y { } <--- nameForEmit should be "X"
370 // export { Y as X };
371 // Set of names that should NOT be used when generating a unique nameForEmit
372 const usedNames = new Set();
373 // First collect the names of explicit package exports, and perform a sanity check.
374 for (const entity of this._entities) {
375 for (const exportName of entity.exportNames) {
376 if (usedNames.has(exportName)) {
377 // This should be impossible
378 throw new node_core_library_1.InternalError(`A package cannot have two exports with the name "${exportName}"`);
379 }
380 usedNames.add(exportName);
381 }
382 }
383 // Ensure that each entity has a unique nameForEmit
384 for (const entity of this._entities) {
385 // What name would we ideally want to emit it as?
386 let idealNameForEmit;
387 // If this entity is exported exactly once, then we prefer the exported name
388 if (entity.singleExportName !== undefined &&
389 entity.singleExportName !== ts.InternalSymbolName.Default) {
390 idealNameForEmit = entity.singleExportName;
391 }
392 else {
393 // otherwise use the local name
394 idealNameForEmit = entity.astEntity.localName;
395 }
396 if (idealNameForEmit.includes('.')) {
397 // For an ImportType with a namespace chain, only the top namespace is imported.
398 idealNameForEmit = idealNameForEmit.split('.')[0];
399 }
400 // If the idealNameForEmit happens to be the same as one of the exports, then we're safe to use that...
401 if (entity.exportNames.has(idealNameForEmit)) {
402 // ...except that if it conflicts with a global name, then the global name wins
403 if (!this.globalVariableAnalyzer.hasGlobalName(idealNameForEmit)) {
404 // ...also avoid "default" which can interfere with "export { default } from 'some-module;'"
405 if (idealNameForEmit !== 'default') {
406 entity.nameForEmit = idealNameForEmit;
407 continue;
408 }
409 }
410 }
411 // Generate a unique name based on idealNameForEmit
412 let suffix = 1;
413 let nameForEmit = idealNameForEmit;
414 // Choose a name that doesn't conflict with usedNames or a global name
415 while (nameForEmit === 'default' ||
416 usedNames.has(nameForEmit) ||
417 this.globalVariableAnalyzer.hasGlobalName(nameForEmit)) {
418 nameForEmit = `${idealNameForEmit}_${++suffix}`;
419 }
420 entity.nameForEmit = nameForEmit;
421 usedNames.add(nameForEmit);
422 }
423 }
424 _fetchSymbolMetadata(astSymbol) {
425 if (astSymbol.symbolMetadata) {
426 return;
427 }
428 // When we solve an astSymbol, then we always also solve all of its parents and all of its declarations.
429 // The parent is solved first.
430 if (astSymbol.parentAstSymbol && astSymbol.parentAstSymbol.symbolMetadata === undefined) {
431 this._fetchSymbolMetadata(astSymbol.parentAstSymbol);
432 }
433 // Construct the DeclarationMetadata objects, and detect any ancillary declarations
434 this._calculateDeclarationMetadataForDeclarations(astSymbol);
435 // Calculate the ApiItemMetadata objects
436 for (const astDeclaration of astSymbol.astDeclarations) {
437 this._calculateApiItemMetadata(astDeclaration);
438 }
439 // The most public effectiveReleaseTag for all declarations
440 let maxEffectiveReleaseTag = api_extractor_model_1.ReleaseTag.None;
441 for (const astDeclaration of astSymbol.astDeclarations) {
442 // We know we solved this above
443 const apiItemMetadata = astDeclaration.apiItemMetadata;
444 const effectiveReleaseTag = apiItemMetadata.effectiveReleaseTag;
445 if (effectiveReleaseTag > maxEffectiveReleaseTag) {
446 maxEffectiveReleaseTag = effectiveReleaseTag;
447 }
448 }
449 // Update this last when we're sure no exceptions were thrown
450 astSymbol.symbolMetadata = new SymbolMetadata_1.SymbolMetadata({
451 maxEffectiveReleaseTag
452 });
453 }
454 _calculateDeclarationMetadataForDeclarations(astSymbol) {
455 // Initialize DeclarationMetadata for each declaration
456 for (const astDeclaration of astSymbol.astDeclarations) {
457 if (astDeclaration.declarationMetadata) {
458 throw new node_core_library_1.InternalError('AstDeclaration.declarationMetadata is not expected to have been initialized yet');
459 }
460 const metadata = new DeclarationMetadata_1.InternalDeclarationMetadata();
461 metadata.tsdocParserContext = this._parseTsdocForAstDeclaration(astDeclaration);
462 astDeclaration.declarationMetadata = metadata;
463 }
464 // Detect ancillary declarations
465 for (const astDeclaration of astSymbol.astDeclarations) {
466 // For a getter/setter pair, make the setter ancillary to the getter
467 if (astDeclaration.declaration.kind === ts.SyntaxKind.SetAccessor) {
468 let foundGetter = false;
469 for (const getterAstDeclaration of astDeclaration.astSymbol.astDeclarations) {
470 if (getterAstDeclaration.declaration.kind === ts.SyntaxKind.GetAccessor) {
471 // Associate it with the getter
472 this._addAncillaryDeclaration(getterAstDeclaration, astDeclaration);
473 foundGetter = true;
474 }
475 }
476 if (!foundGetter) {
477 this.messageRouter.addAnalyzerIssue("ae-missing-getter" /* MissingGetter */, `The property "${astDeclaration.astSymbol.localName}" has a setter but no getter.`, astDeclaration);
478 }
479 }
480 }
481 }
482 _addAncillaryDeclaration(mainAstDeclaration, ancillaryAstDeclaration) {
483 const mainMetadata = mainAstDeclaration.declarationMetadata;
484 const ancillaryMetadata = ancillaryAstDeclaration.declarationMetadata;
485 if (mainMetadata.ancillaryDeclarations.indexOf(ancillaryAstDeclaration) >= 0) {
486 return; // already added
487 }
488 if (mainAstDeclaration.astSymbol !== ancillaryAstDeclaration.astSymbol) {
489 throw new node_core_library_1.InternalError('Invalid call to _addAncillaryDeclaration() because declarations do not' +
490 ' belong to the same symbol');
491 }
492 if (mainMetadata.isAncillary) {
493 throw new node_core_library_1.InternalError('Invalid call to _addAncillaryDeclaration() because the target is ancillary itself');
494 }
495 if (ancillaryMetadata.isAncillary) {
496 throw new node_core_library_1.InternalError('Invalid call to _addAncillaryDeclaration() because source is already ancillary' +
497 ' to another declaration');
498 }
499 if (mainAstDeclaration.apiItemMetadata || ancillaryAstDeclaration.apiItemMetadata) {
500 throw new node_core_library_1.InternalError('Invalid call to _addAncillaryDeclaration() because the API item metadata' +
501 ' has already been constructed');
502 }
503 ancillaryMetadata.isAncillary = true;
504 mainMetadata.ancillaryDeclarations.push(ancillaryAstDeclaration);
505 }
506 _calculateApiItemMetadata(astDeclaration) {
507 const declarationMetadata = astDeclaration.declarationMetadata;
508 if (declarationMetadata.isAncillary) {
509 if (astDeclaration.declaration.kind === ts.SyntaxKind.SetAccessor) {
510 if (declarationMetadata.tsdocParserContext) {
511 this.messageRouter.addAnalyzerIssue("ae-setter-with-docs" /* SetterWithDocs */, `The doc comment for the property "${astDeclaration.astSymbol.localName}"` +
512 ` must appear on the getter, not the setter.`, astDeclaration);
513 }
514 }
515 // We never calculate ApiItemMetadata for an ancillary declaration; instead, it is assigned when
516 // the main declaration is processed.
517 return;
518 }
519 const options = {
520 declaredReleaseTag: api_extractor_model_1.ReleaseTag.None,
521 effectiveReleaseTag: api_extractor_model_1.ReleaseTag.None,
522 isEventProperty: false,
523 isOverride: false,
524 isSealed: false,
525 isVirtual: false,
526 isPreapproved: false,
527 releaseTagSameAsParent: false
528 };
529 const parserContext = declarationMetadata.tsdocParserContext;
530 if (parserContext) {
531 const modifierTagSet = parserContext.docComment.modifierTagSet;
532 let declaredReleaseTag = api_extractor_model_1.ReleaseTag.None;
533 let extraReleaseTags = false;
534 if (modifierTagSet.isPublic()) {
535 declaredReleaseTag = api_extractor_model_1.ReleaseTag.Public;
536 }
537 if (modifierTagSet.isBeta()) {
538 if (declaredReleaseTag !== api_extractor_model_1.ReleaseTag.None) {
539 extraReleaseTags = true;
540 }
541 else {
542 declaredReleaseTag = api_extractor_model_1.ReleaseTag.Beta;
543 }
544 }
545 if (modifierTagSet.isAlpha()) {
546 if (declaredReleaseTag !== api_extractor_model_1.ReleaseTag.None) {
547 extraReleaseTags = true;
548 }
549 else {
550 declaredReleaseTag = api_extractor_model_1.ReleaseTag.Alpha;
551 }
552 }
553 if (modifierTagSet.isInternal()) {
554 if (declaredReleaseTag !== api_extractor_model_1.ReleaseTag.None) {
555 extraReleaseTags = true;
556 }
557 else {
558 declaredReleaseTag = api_extractor_model_1.ReleaseTag.Internal;
559 }
560 }
561 if (extraReleaseTags) {
562 if (!astDeclaration.astSymbol.isExternal) {
563 // for now, don't report errors for external code
564 this.messageRouter.addAnalyzerIssue("ae-extra-release-tag" /* ExtraReleaseTag */, 'The doc comment should not contain more than one release tag', astDeclaration);
565 }
566 }
567 options.declaredReleaseTag = declaredReleaseTag;
568 options.isEventProperty = modifierTagSet.isEventProperty();
569 options.isOverride = modifierTagSet.isOverride();
570 options.isSealed = modifierTagSet.isSealed();
571 options.isVirtual = modifierTagSet.isVirtual();
572 const preapprovedTag = this.extractorConfig.tsdocConfiguration.tryGetTagDefinition('@preapproved');
573 if (preapprovedTag && modifierTagSet.hasTag(preapprovedTag)) {
574 // This feature only makes sense for potentially big declarations.
575 switch (astDeclaration.declaration.kind) {
576 case ts.SyntaxKind.ClassDeclaration:
577 case ts.SyntaxKind.EnumDeclaration:
578 case ts.SyntaxKind.InterfaceDeclaration:
579 case ts.SyntaxKind.ModuleDeclaration:
580 if (declaredReleaseTag === api_extractor_model_1.ReleaseTag.Internal) {
581 options.isPreapproved = true;
582 }
583 else {
584 this.messageRouter.addAnalyzerIssue("ae-preapproved-bad-release-tag" /* PreapprovedBadReleaseTag */, `The @preapproved tag cannot be applied to "${astDeclaration.astSymbol.localName}"` +
585 ` without an @internal release tag`, astDeclaration);
586 }
587 break;
588 default:
589 this.messageRouter.addAnalyzerIssue("ae-preapproved-unsupported-type" /* PreapprovedUnsupportedType */, `The @preapproved tag cannot be applied to "${astDeclaration.astSymbol.localName}"` +
590 ` because it is not a supported declaration type`, astDeclaration);
591 break;
592 }
593 }
594 }
595 // This needs to be set regardless of whether or not a parserContext exists
596 if (astDeclaration.parent) {
597 const parentApiItemMetadata = this.fetchApiItemMetadata(astDeclaration.parent);
598 options.effectiveReleaseTag =
599 options.declaredReleaseTag === api_extractor_model_1.ReleaseTag.None
600 ? parentApiItemMetadata.effectiveReleaseTag
601 : options.declaredReleaseTag;
602 options.releaseTagSameAsParent =
603 parentApiItemMetadata.effectiveReleaseTag === options.effectiveReleaseTag;
604 }
605 else {
606 options.effectiveReleaseTag = options.declaredReleaseTag;
607 }
608 if (options.effectiveReleaseTag === api_extractor_model_1.ReleaseTag.None) {
609 if (!astDeclaration.astSymbol.isExternal) {
610 // for now, don't report errors for external code
611 // Don't report missing release tags for forgotten exports
612 const astSymbol = astDeclaration.astSymbol;
613 const entity = this._entitiesByAstEntity.get(astSymbol.rootAstSymbol);
614 if (entity && entity.consumable) {
615 // We also don't report errors for the default export of an entry point, since its doc comment
616 // isn't easy to obtain from the .d.ts file
617 if (astSymbol.rootAstSymbol.localName !== '_default') {
618 this.messageRouter.addAnalyzerIssue("ae-missing-release-tag" /* MissingReleaseTag */, `"${entity.astEntity.localName}" is exported by the package, but it is missing ` +
619 `a release tag (@alpha, @beta, @public, or @internal)`, astSymbol);
620 }
621 }
622 }
623 options.effectiveReleaseTag = api_extractor_model_1.ReleaseTag.Public;
624 }
625 const apiItemMetadata = new ApiItemMetadata_1.ApiItemMetadata(options);
626 if (parserContext) {
627 apiItemMetadata.tsdocComment = parserContext.docComment;
628 }
629 astDeclaration.apiItemMetadata = apiItemMetadata;
630 // Lastly, share the result with any ancillary declarations
631 for (const ancillaryDeclaration of declarationMetadata.ancillaryDeclarations) {
632 ancillaryDeclaration.apiItemMetadata = apiItemMetadata;
633 }
634 }
635 _parseTsdocForAstDeclaration(astDeclaration) {
636 const declaration = astDeclaration.declaration;
637 let nodeForComment = declaration;
638 if (ts.isVariableDeclaration(declaration)) {
639 // Variable declarations are special because they can be combined into a list. For example:
640 //
641 // /** A */ export /** B */ const /** C */ x = 1, /** D **/ [ /** E */ y, z] = [3, 4];
642 //
643 // The compiler will only emit comments A and C in the .d.ts file, so in general there isn't a well-defined
644 // way to document these parts. API Extractor requires you to break them into separate exports like this:
645 //
646 // /** A */ export const x = 1;
647 //
648 // But _getReleaseTagForDeclaration() still receives a node corresponding to "x", so we need to walk upwards
649 // and find the containing statement in order for getJSDocCommentRanges() to read the comment that we expect.
650 const statement = TypeScriptHelpers_1.TypeScriptHelpers.findFirstParent(declaration, ts.SyntaxKind.VariableStatement);
651 if (statement !== undefined) {
652 // For a compound declaration, fall back to looking for C instead of A
653 if (statement.declarationList.declarations.length === 1) {
654 nodeForComment = statement;
655 }
656 }
657 }
658 const sourceFileText = declaration.getSourceFile().text;
659 const ranges = TypeScriptInternals_1.TypeScriptInternals.getJSDocCommentRanges(nodeForComment, sourceFileText) || [];
660 if (ranges.length === 0) {
661 return undefined;
662 }
663 // We use the JSDoc comment block that is closest to the definition, i.e.
664 // the last one preceding it
665 const range = ranges[ranges.length - 1];
666 const tsdocTextRange = tsdoc.TextRange.fromStringRange(sourceFileText, range.pos, range.end);
667 const parserContext = this._tsdocParser.parseRange(tsdocTextRange);
668 this.messageRouter.addTsdocMessages(parserContext, declaration.getSourceFile(), astDeclaration);
669 // We delete the @privateRemarks block as early as possible, to ensure that it never leaks through
670 // into one of the output files.
671 parserContext.docComment.privateRemarks = undefined;
672 return parserContext;
673 }
674 _collectReferenceDirectives(astEntity) {
675 if (astEntity instanceof AstSymbol_1.AstSymbol) {
676 const sourceFiles = astEntity.astDeclarations.map((astDeclaration) => astDeclaration.declaration.getSourceFile());
677 return this._collectReferenceDirectivesFromSourceFiles(sourceFiles);
678 }
679 if (astEntity instanceof AstNamespaceImport_1.AstNamespaceImport) {
680 const sourceFiles = [astEntity.astModule.sourceFile];
681 return this._collectReferenceDirectivesFromSourceFiles(sourceFiles);
682 }
683 }
684 _collectReferenceDirectivesFromSourceFiles(sourceFiles) {
685 const seenFilenames = new Set();
686 for (const sourceFile of sourceFiles) {
687 if (sourceFile && sourceFile.fileName) {
688 if (!seenFilenames.has(sourceFile.fileName)) {
689 seenFilenames.add(sourceFile.fileName);
690 for (const typeReferenceDirective of sourceFile.typeReferenceDirectives) {
691 const name = sourceFile.text.substring(typeReferenceDirective.pos, typeReferenceDirective.end);
692 this._dtsTypeReferenceDirectives.add(name);
693 }
694 for (const libReferenceDirective of sourceFile.libReferenceDirectives) {
695 const name = sourceFile.text.substring(libReferenceDirective.pos, libReferenceDirective.end);
696 this._dtsLibReferenceDirectives.add(name);
697 }
698 }
699 }
700 }
701 }
702}
703exports.Collector = Collector;
704//# sourceMappingURL=Collector.js.map
\No newline at end of file