UNPKG

22.8 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.DtsRollupGenerator = exports.DtsRollupKind = void 0;
29/* eslint-disable no-bitwise */
30const ts = __importStar(require("typescript"));
31const node_core_library_1 = require("@rushstack/node-core-library");
32const api_extractor_model_1 = require("@microsoft/api-extractor-model");
33const TypeScriptHelpers_1 = require("../analyzer/TypeScriptHelpers");
34const Span_1 = require("../analyzer/Span");
35const AstImport_1 = require("../analyzer/AstImport");
36const AstDeclaration_1 = require("../analyzer/AstDeclaration");
37const AstSymbol_1 = require("../analyzer/AstSymbol");
38const IndentedWriter_1 = require("./IndentedWriter");
39const DtsEmitHelpers_1 = require("./DtsEmitHelpers");
40const AstNamespaceImport_1 = require("../analyzer/AstNamespaceImport");
41const SourceFileLocationFormatter_1 = require("../analyzer/SourceFileLocationFormatter");
42/**
43 * Used with DtsRollupGenerator.writeTypingsFile()
44 */
45var DtsRollupKind;
46(function (DtsRollupKind) {
47 /**
48 * Generate a *.d.ts file for an internal release, or for the trimming=false mode.
49 * This output file will contain all definitions that are reachable from the entry point.
50 */
51 DtsRollupKind[DtsRollupKind["InternalRelease"] = 0] = "InternalRelease";
52 /**
53 * Generate a *.d.ts file for a preview release.
54 * This output file will contain all definitions that are reachable from the entry point,
55 * except definitions marked as \@internal.
56 */
57 DtsRollupKind[DtsRollupKind["AlphaRelease"] = 1] = "AlphaRelease";
58 /**
59 * Generate a *.d.ts file for a preview release.
60 * This output file will contain all definitions that are reachable from the entry point,
61 * except definitions marked as \@alpha or \@internal.
62 */
63 DtsRollupKind[DtsRollupKind["BetaRelease"] = 2] = "BetaRelease";
64 /**
65 * Generate a *.d.ts file for a public release.
66 * This output file will contain all definitions that are reachable from the entry point,
67 * except definitions marked as \@beta, \@alpha, or \@internal.
68 */
69 DtsRollupKind[DtsRollupKind["PublicRelease"] = 3] = "PublicRelease";
70})(DtsRollupKind = exports.DtsRollupKind || (exports.DtsRollupKind = {}));
71class DtsRollupGenerator {
72 /**
73 * Generates the typings file and writes it to disk.
74 *
75 * @param dtsFilename - The *.d.ts output filename
76 */
77 static writeTypingsFile(collector, dtsFilename, dtsKind, newlineKind) {
78 const writer = new IndentedWriter_1.IndentedWriter();
79 writer.trimLeadingSpaces = true;
80 DtsRollupGenerator._generateTypingsFileContent(collector, writer, dtsKind);
81 node_core_library_1.FileSystem.writeFile(dtsFilename, writer.toString(), {
82 convertLineEndings: newlineKind,
83 ensureFolderExists: true
84 });
85 }
86 static _generateTypingsFileContent(collector, writer, dtsKind) {
87 // Emit the @packageDocumentation comment at the top of the file
88 if (collector.workingPackage.tsdocParserContext) {
89 writer.trimLeadingSpaces = false;
90 writer.writeLine(collector.workingPackage.tsdocParserContext.sourceRange.toString());
91 writer.trimLeadingSpaces = true;
92 writer.ensureSkippedLine();
93 }
94 // Emit the triple slash directives
95 for (const typeDirectiveReference of collector.dtsTypeReferenceDirectives) {
96 // https://github.com/microsoft/TypeScript/blob/611ebc7aadd7a44a4c0447698bfda9222a78cb66/src/compiler/declarationEmitter.ts#L162
97 writer.writeLine(`/// <reference types="${typeDirectiveReference}" />`);
98 }
99 for (const libDirectiveReference of collector.dtsLibReferenceDirectives) {
100 writer.writeLine(`/// <reference lib="${libDirectiveReference}" />`);
101 }
102 writer.ensureSkippedLine();
103 // Emit the imports
104 for (const entity of collector.entities) {
105 if (entity.astEntity instanceof AstImport_1.AstImport) {
106 const astImport = entity.astEntity;
107 // For example, if the imported API comes from an external package that supports AEDoc,
108 // and it was marked as `@internal`, then don't emit it.
109 const symbolMetadata = collector.tryFetchMetadataForAstEntity(astImport);
110 const maxEffectiveReleaseTag = symbolMetadata
111 ? symbolMetadata.maxEffectiveReleaseTag
112 : api_extractor_model_1.ReleaseTag.None;
113 if (this._shouldIncludeReleaseTag(maxEffectiveReleaseTag, dtsKind)) {
114 DtsEmitHelpers_1.DtsEmitHelpers.emitImport(writer, entity, astImport);
115 }
116 }
117 }
118 writer.ensureSkippedLine();
119 // Emit the regular declarations
120 for (const entity of collector.entities) {
121 const astEntity = entity.astEntity;
122 const symbolMetadata = collector.tryFetchMetadataForAstEntity(astEntity);
123 const maxEffectiveReleaseTag = symbolMetadata
124 ? symbolMetadata.maxEffectiveReleaseTag
125 : api_extractor_model_1.ReleaseTag.None;
126 if (!this._shouldIncludeReleaseTag(maxEffectiveReleaseTag, dtsKind)) {
127 if (!collector.extractorConfig.omitTrimmingComments) {
128 writer.ensureSkippedLine();
129 writer.writeLine(`/* Excluded from this release type: ${entity.nameForEmit} */`);
130 }
131 continue;
132 }
133 if (astEntity instanceof AstSymbol_1.AstSymbol) {
134 // Emit all the declarations for this entry
135 for (const astDeclaration of astEntity.astDeclarations || []) {
136 const apiItemMetadata = collector.fetchApiItemMetadata(astDeclaration);
137 if (!this._shouldIncludeReleaseTag(apiItemMetadata.effectiveReleaseTag, dtsKind)) {
138 if (!collector.extractorConfig.omitTrimmingComments) {
139 writer.ensureSkippedLine();
140 writer.writeLine(`/* Excluded declaration from this release type: ${entity.nameForEmit} */`);
141 }
142 continue;
143 }
144 else {
145 const span = new Span_1.Span(astDeclaration.declaration);
146 DtsRollupGenerator._modifySpan(collector, span, entity, astDeclaration, dtsKind);
147 writer.ensureSkippedLine();
148 span.writeModifiedText(writer);
149 writer.ensureNewLine();
150 }
151 }
152 }
153 if (astEntity instanceof AstNamespaceImport_1.AstNamespaceImport) {
154 const astModuleExportInfo = astEntity.fetchAstModuleExportInfo(collector);
155 if (entity.nameForEmit === undefined) {
156 // This should never happen
157 throw new node_core_library_1.InternalError('referencedEntry.nameForEmit is undefined');
158 }
159 if (astModuleExportInfo.starExportedExternalModules.size > 0) {
160 // We could support this, but we would need to find a way to safely represent it.
161 throw new Error(`The ${entity.nameForEmit} namespace import includes a start export, which is not supported:\n` +
162 SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(astEntity.declaration));
163 }
164 // Emit a synthetic declaration for the namespace. It will look like this:
165 //
166 // declare namespace example {
167 // export {
168 // f1,
169 // f2
170 // }
171 // }
172 //
173 // Note that we do not try to relocate f1()/f2() to be inside the namespace because other type
174 // signatures may reference them directly (without using the namespace qualifier).
175 writer.ensureSkippedLine();
176 if (entity.shouldInlineExport) {
177 writer.write('export ');
178 }
179 writer.writeLine(`declare namespace ${entity.nameForEmit} {`);
180 // all local exports of local imported module are just references to top-level declarations
181 writer.increaseIndent();
182 writer.writeLine('export {');
183 writer.increaseIndent();
184 const exportClauses = [];
185 for (const [exportedName, exportedEntity] of astModuleExportInfo.exportedLocalEntities) {
186 const collectorEntity = collector.tryGetCollectorEntity(exportedEntity);
187 if (collectorEntity === undefined) {
188 // This should never happen
189 // top-level exports of local imported module should be added as collector entities before
190 throw new node_core_library_1.InternalError(`Cannot find collector entity for ${entity.nameForEmit}.${exportedEntity.localName}`);
191 }
192 if (collectorEntity.nameForEmit === exportedName) {
193 exportClauses.push(collectorEntity.nameForEmit);
194 }
195 else {
196 exportClauses.push(`${collectorEntity.nameForEmit} as ${exportedName}`);
197 }
198 }
199 writer.writeLine(exportClauses.join(',\n'));
200 writer.decreaseIndent();
201 writer.writeLine('}'); // end of "export { ... }"
202 writer.decreaseIndent();
203 writer.writeLine('}'); // end of "declare namespace { ... }"
204 }
205 if (!entity.shouldInlineExport) {
206 for (const exportName of entity.exportNames) {
207 DtsEmitHelpers_1.DtsEmitHelpers.emitNamedExport(writer, exportName, entity);
208 }
209 }
210 writer.ensureSkippedLine();
211 }
212 DtsEmitHelpers_1.DtsEmitHelpers.emitStarExports(writer, collector);
213 // Emit "export { }" which is a special directive that prevents consumers from importing declarations
214 // that don't have an explicit "export" modifier.
215 writer.ensureSkippedLine();
216 writer.writeLine('export { }');
217 }
218 /**
219 * Before writing out a declaration, _modifySpan() applies various fixups to make it nice.
220 */
221 static _modifySpan(collector, span, entity, astDeclaration, dtsKind) {
222 const previousSpan = span.previousSibling;
223 let recurseChildren = true;
224 switch (span.kind) {
225 case ts.SyntaxKind.JSDocComment:
226 // If the @packageDocumentation comment seems to be attached to one of the regular API items,
227 // omit it. It gets explictly emitted at the top of the file.
228 if (span.node.getText().match(/(?:\s|\*)@packageDocumentation(?:\s|\*)/gi)) {
229 span.modification.skipAll();
230 }
231 // For now, we don't transform JSDoc comment nodes at all
232 recurseChildren = false;
233 break;
234 case ts.SyntaxKind.ExportKeyword:
235 case ts.SyntaxKind.DefaultKeyword:
236 case ts.SyntaxKind.DeclareKeyword:
237 // Delete any explicit "export" or "declare" keywords -- we will re-add them below
238 span.modification.skipAll();
239 break;
240 case ts.SyntaxKind.InterfaceKeyword:
241 case ts.SyntaxKind.ClassKeyword:
242 case ts.SyntaxKind.EnumKeyword:
243 case ts.SyntaxKind.NamespaceKeyword:
244 case ts.SyntaxKind.ModuleKeyword:
245 case ts.SyntaxKind.TypeKeyword:
246 case ts.SyntaxKind.FunctionKeyword:
247 // Replace the stuff we possibly deleted above
248 let replacedModifiers = '';
249 // Add a declare statement for root declarations (but not for nested declarations)
250 if (!astDeclaration.parent) {
251 replacedModifiers += 'declare ';
252 }
253 if (entity.shouldInlineExport) {
254 replacedModifiers = 'export ' + replacedModifiers;
255 }
256 if (previousSpan && previousSpan.kind === ts.SyntaxKind.SyntaxList) {
257 // If there is a previous span of type SyntaxList, then apply it before any other modifiers
258 // (e.g. "abstract") that appear there.
259 previousSpan.modification.prefix = replacedModifiers + previousSpan.modification.prefix;
260 }
261 else {
262 // Otherwise just stick it in front of this span
263 span.modification.prefix = replacedModifiers + span.modification.prefix;
264 }
265 break;
266 case ts.SyntaxKind.VariableDeclaration:
267 // Is this a top-level variable declaration?
268 // (The logic below does not apply to variable declarations that are part of an explicit "namespace" block,
269 // since the compiler prefers not to emit "declare" or "export" keywords for those declarations.)
270 if (!span.parent) {
271 // The VariableDeclaration node is part of a VariableDeclarationList, however
272 // the Entry.followedSymbol points to the VariableDeclaration part because
273 // multiple definitions might share the same VariableDeclarationList.
274 //
275 // Since we are emitting a separate declaration for each one, we need to look upwards
276 // in the ts.Node tree and write a copy of the enclosing VariableDeclarationList
277 // content (e.g. "var" from "var x=1, y=2").
278 const list = TypeScriptHelpers_1.TypeScriptHelpers.matchAncestor(span.node, [
279 ts.SyntaxKind.VariableDeclarationList,
280 ts.SyntaxKind.VariableDeclaration
281 ]);
282 if (!list) {
283 // This should not happen unless the compiler API changes somehow
284 throw new node_core_library_1.InternalError('Unsupported variable declaration');
285 }
286 const listPrefix = list
287 .getSourceFile()
288 .text.substring(list.getStart(), list.declarations[0].getStart());
289 span.modification.prefix = 'declare ' + listPrefix + span.modification.prefix;
290 span.modification.suffix = ';';
291 if (entity.shouldInlineExport) {
292 span.modification.prefix = 'export ' + span.modification.prefix;
293 }
294 const declarationMetadata = collector.fetchDeclarationMetadata(astDeclaration);
295 if (declarationMetadata.tsdocParserContext) {
296 // Typically the comment for a variable declaration is attached to the outer variable statement
297 // (which may possibly contain multiple variable declarations), so it's not part of the Span.
298 // Instead we need to manually inject it.
299 let originalComment = declarationMetadata.tsdocParserContext.sourceRange.toString();
300 if (!/\r?\n\s*$/.test(originalComment)) {
301 originalComment += '\n';
302 }
303 span.modification.indentDocComment = Span_1.IndentDocCommentScope.PrefixOnly;
304 span.modification.prefix = originalComment + span.modification.prefix;
305 }
306 }
307 break;
308 case ts.SyntaxKind.Identifier:
309 {
310 const referencedEntity = collector.tryGetEntityForNode(span.node);
311 if (referencedEntity) {
312 if (!referencedEntity.nameForEmit) {
313 // This should never happen
314 throw new node_core_library_1.InternalError('referencedEntry.nameForEmit is undefined');
315 }
316 span.modification.prefix = referencedEntity.nameForEmit;
317 // For debugging:
318 // span.modification.prefix += '/*R=FIX*/';
319 }
320 else {
321 // For debugging:
322 // span.modification.prefix += '/*R=KEEP*/';
323 }
324 }
325 break;
326 case ts.SyntaxKind.ImportType:
327 DtsEmitHelpers_1.DtsEmitHelpers.modifyImportTypeSpan(collector, span, astDeclaration, (childSpan, childAstDeclaration) => {
328 DtsRollupGenerator._modifySpan(collector, childSpan, entity, childAstDeclaration, dtsKind);
329 });
330 break;
331 }
332 if (recurseChildren) {
333 for (const child of span.children) {
334 let childAstDeclaration = astDeclaration;
335 // Should we trim this node?
336 let trimmed = false;
337 if (AstDeclaration_1.AstDeclaration.isSupportedSyntaxKind(child.kind)) {
338 childAstDeclaration = collector.astSymbolTable.getChildAstDeclarationByNode(child.node, astDeclaration);
339 const releaseTag = collector.fetchApiItemMetadata(childAstDeclaration).effectiveReleaseTag;
340 if (!this._shouldIncludeReleaseTag(releaseTag, dtsKind)) {
341 let nodeToTrim = child;
342 // If we are trimming a variable statement, then we need to trim the outer VariableDeclarationList
343 // as well.
344 if (child.kind === ts.SyntaxKind.VariableDeclaration) {
345 const variableStatement = child.findFirstParent(ts.SyntaxKind.VariableStatement);
346 if (variableStatement !== undefined) {
347 nodeToTrim = variableStatement;
348 }
349 }
350 const modification = nodeToTrim.modification;
351 // Yes, trim it and stop here
352 const name = childAstDeclaration.astSymbol.localName;
353 modification.omitChildren = true;
354 if (!collector.extractorConfig.omitTrimmingComments) {
355 modification.prefix = `/* Excluded from this release type: ${name} */`;
356 }
357 else {
358 modification.prefix = '';
359 }
360 modification.suffix = '';
361 if (nodeToTrim.children.length > 0) {
362 // If there are grandchildren, then keep the last grandchild's separator,
363 // since it often has useful whitespace
364 modification.suffix = nodeToTrim.children[nodeToTrim.children.length - 1].separator;
365 }
366 if (nodeToTrim.nextSibling) {
367 // If the thing we are trimming is followed by a comma, then trim the comma also.
368 // An example would be an enum member.
369 if (nodeToTrim.nextSibling.kind === ts.SyntaxKind.CommaToken) {
370 // Keep its separator since it often has useful whitespace
371 modification.suffix += nodeToTrim.nextSibling.separator;
372 nodeToTrim.nextSibling.modification.skipAll();
373 }
374 }
375 trimmed = true;
376 }
377 }
378 if (!trimmed) {
379 DtsRollupGenerator._modifySpan(collector, child, entity, childAstDeclaration, dtsKind);
380 }
381 }
382 }
383 }
384 static _shouldIncludeReleaseTag(releaseTag, dtsKind) {
385 switch (dtsKind) {
386 case DtsRollupKind.InternalRelease:
387 return true;
388 case DtsRollupKind.AlphaRelease:
389 return (releaseTag === api_extractor_model_1.ReleaseTag.Alpha ||
390 releaseTag === api_extractor_model_1.ReleaseTag.Beta ||
391 releaseTag === api_extractor_model_1.ReleaseTag.Public ||
392 // NOTE: If the release tag is "None", then we don't have enough information to trim it
393 releaseTag === api_extractor_model_1.ReleaseTag.None);
394 case DtsRollupKind.BetaRelease:
395 return (releaseTag === api_extractor_model_1.ReleaseTag.Beta ||
396 releaseTag === api_extractor_model_1.ReleaseTag.Public ||
397 // NOTE: If the release tag is "None", then we don't have enough information to trim it
398 releaseTag === api_extractor_model_1.ReleaseTag.None);
399 case DtsRollupKind.PublicRelease:
400 return releaseTag === api_extractor_model_1.ReleaseTag.Public || releaseTag === api_extractor_model_1.ReleaseTag.None;
401 default:
402 throw new Error(`${DtsRollupKind[dtsKind]} is not implemented`);
403 }
404 }
405}
406exports.DtsRollupGenerator = DtsRollupGenerator;
407//# sourceMappingURL=DtsRollupGenerator.js.map
\No newline at end of file