UNPKG

109 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Assembler = void 0;
4const crypto = require("node:crypto");
5const fs = require("node:fs");
6const path = require("node:path");
7const spec = require("@jsii/spec");
8const spec_1 = require("@jsii/spec");
9const chalk = require("chalk");
10const deepEqual = require("fast-deep-equal/es6");
11const log4js = require("log4js");
12const ts = require("typescript");
13const Case = require("./case");
14const symbol_id_1 = require("./common/symbol-id");
15const directives_1 = require("./directives");
16const docs_1 = require("./docs");
17const jsii_diagnostic_1 = require("./jsii-diagnostic");
18const literate = require("./literate");
19const bindings = require("./node-bindings");
20const reserved_words_1 = require("./reserved-words");
21const deprecated_remover_1 = require("./transforms/deprecated-remover");
22const deprecation_warnings_1 = require("./transforms/deprecation-warnings");
23const runtime_info_1 = require("./transforms/runtime-info");
24const utils_1 = require("./transforms/utils");
25const validator_1 = require("./validator");
26const version_1 = require("./version");
27const warnings_1 = require("./warnings");
28// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
29const sortJson = require('sort-json');
30const LOG = log4js.getLogger('jsii/assembler');
31/**
32 * The JSII Assembler consumes a ``ts.Program`` instance and emits a JSII assembly.
33 */
34class Assembler {
35 /**
36 * @param projectInfo information about the package being assembled
37 * @param program the TypeScript program to be assembled from
38 * @param stdlib the directory where the TypeScript stdlib is rooted
39 */
40 constructor(projectInfo, system, program, stdlib, options = {}) {
41 this.projectInfo = projectInfo;
42 this.system = system;
43 this.program = program;
44 this.stdlib = stdlib;
45 this._diagnostics = new Array();
46 this._deferred = new Array();
47 this._types = new Map();
48 this._packageInfoCache = new Map();
49 /** Map of Symbol to namespace export Symbol */
50 this._submoduleMap = new Map();
51 /**
52 * Submodule information
53 *
54 * Contains submodule information for all namespaces that have been seen
55 * across all assemblies (this and dependencies).
56 *
57 * Filtered to local submodules only at time of writing the assembly out to disk.
58 */
59 this._submodules = new Map();
60 this._typeChecker = this.program.getTypeChecker();
61 if (options.stripDeprecated) {
62 let allowlistedDeprecations;
63 if (options.stripDeprecatedAllowListFile) {
64 if (!fs.existsSync(options.stripDeprecatedAllowListFile)) {
65 throw new Error(`--strip-deprecated file not found: ${options.stripDeprecatedAllowListFile}`);
66 }
67 allowlistedDeprecations = new Set(fs.readFileSync(options.stripDeprecatedAllowListFile, 'utf8').split('\n'));
68 }
69 this.deprecatedRemover = new deprecated_remover_1.DeprecatedRemover(this._typeChecker, allowlistedDeprecations);
70 }
71 if (options.addDeprecationWarnings) {
72 this.warningsInjector = new deprecation_warnings_1.DeprecationWarningsInjector(this._typeChecker);
73 }
74 this.compressAssembly = options.compressAssembly;
75 const dts = projectInfo.types;
76 let mainFile = dts.replace(/\.d\.ts(x?)$/, '.ts$1');
77 // If out-of-source build was configured (tsc's outDir and rootDir), the
78 // main file's path needs to be re-rooted from the outDir into the rootDir.
79 const tscOutDir = program.getCompilerOptions().outDir;
80 if (tscOutDir != null) {
81 mainFile = path.relative(tscOutDir, mainFile);
82 // rootDir may be set explicitly or not. If not, inferRootDir replicates
83 // tsc's behavior of using the longest prefix of all built source files.
84 this.tscRootDir = program.getCompilerOptions().rootDir ?? inferRootDir(program);
85 if (this.tscRootDir != null) {
86 mainFile = path.join(this.tscRootDir, mainFile);
87 }
88 }
89 this.mainFile = path.resolve(projectInfo.projectRoot, mainFile);
90 this.runtimeTypeInfoInjector = new runtime_info_1.RuntimeTypeInfoInjector(projectInfo.version);
91 }
92 get customTransformers() {
93 return (0, utils_1.combinedTransformers)(this.deprecatedRemover?.customTransformers ?? {}, this.runtimeTypeInfoInjector.makeTransformers(), this.warningsInjector?.customTransformers ?? {});
94 }
95 /**
96 * Attempt emitting the JSII assembly for the program.
97 *
98 * @return the result of the assembly emission.
99 */
100 emit() {
101 if (!this.projectInfo.description) {
102 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_0001_PKG_MISSING_DESCRIPTION.createDetached());
103 }
104 if (!this.projectInfo.homepage) {
105 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_0002_PKG_MISSING_HOMEPAGE.createDetached());
106 }
107 const readme = _loadReadme.call(this);
108 if (readme == null) {
109 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_0003_MISSING_README.createDetached());
110 }
111 const docs = _loadDocs.call(this);
112 const sourceFile = this.program.getSourceFile(this.mainFile);
113 if (sourceFile == null) {
114 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_0004_COULD_NOT_FIND_ENTRYPOINT.createDetached(this.mainFile));
115 }
116 else {
117 this._registerDependenciesNamespaces(sourceFile);
118 if (LOG.isTraceEnabled()) {
119 LOG.trace(`Processing source file: ${chalk.blue(path.relative(this.projectInfo.projectRoot, sourceFile.fileName))}`);
120 }
121 const symbol = this._typeChecker.getSymbolAtLocation(sourceFile);
122 if (symbol) {
123 const moduleExports = this._typeChecker.getExportsOfModule(symbol);
124 moduleExports.map((item) => this._registerNamespaces(item, this.projectInfo.projectRoot));
125 for (const node of moduleExports) {
126 const decl = node.declarations?.[0];
127 if (decl == null) {
128 continue;
129 }
130 this._visitNode(decl, new EmitContext([], this.projectInfo.stability));
131 }
132 }
133 }
134 this.callDeferredsInOrder();
135 // Skip emitting if any diagnostic message is an error
136 if (this._diagnostics.find((diag) => diag.category === ts.DiagnosticCategory.Error) != null) {
137 LOG.debug('Skipping emit due to errors.');
138 try {
139 return { diagnostics: this._diagnostics, emitSkipped: true };
140 }
141 finally {
142 // Clearing ``this._diagnostics`` to allow contents to be garbage-collected.
143 this._afterEmit();
144 }
145 }
146 const jsiiVersion = this.projectInfo.jsiiVersionFormat === 'short' ? version_1.SHORT_VERSION : version_1.VERSION;
147 const assembly = {
148 schema: spec.SchemaVersion.LATEST,
149 name: this.projectInfo.name,
150 version: this.projectInfo.version,
151 description: this.projectInfo.description ?? this.projectInfo.name,
152 license: this.projectInfo.license,
153 keywords: this.projectInfo.keywords && Array.from(this.projectInfo.keywords),
154 homepage: this.projectInfo.homepage ?? this.projectInfo.repository.url,
155 author: this.projectInfo.author,
156 contributors: this.projectInfo.contributors && [...this.projectInfo.contributors],
157 repository: this.projectInfo.repository,
158 dependencies: noEmptyDict({
159 ...this.projectInfo.dependencies,
160 ...this.projectInfo.peerDependencies,
161 }),
162 dependencyClosure: noEmptyDict(toDependencyClosure(this.projectInfo.dependencyClosure)),
163 bundled: this.projectInfo.bundleDependencies,
164 types: Object.fromEntries(this._types),
165 submodules: noEmptyDict(toSubmoduleDeclarations(this.mySubmodules())),
166 targets: this.projectInfo.targets,
167 metadata: {
168 ...this.projectInfo.metadata,
169 // Downstream consumers need this to map a symbolId in the outDir to a
170 // symbolId in the rootDir.
171 tscRootDir: this.tscRootDir,
172 },
173 docs,
174 readme,
175 jsiiVersion,
176 bin: this.projectInfo.bin,
177 fingerprint: '<TBD>',
178 };
179 if (this.deprecatedRemover) {
180 this._diagnostics.push(...this.deprecatedRemover.removeFrom(assembly));
181 }
182 if (this.warningsInjector) {
183 const jsiiMetadata = {
184 ...(assembly.metadata?.jsii ?? {}),
185 ...{ compiledWithDeprecationWarnings: true },
186 };
187 if (assembly.metadata) {
188 assembly.metadata.jsii = jsiiMetadata;
189 }
190 else {
191 assembly.metadata = { jsii: jsiiMetadata };
192 }
193 this.warningsInjector.process(assembly, this.projectInfo);
194 }
195 const validator = new validator_1.Validator(this.projectInfo, assembly);
196 const validationResult = validator.emit();
197 if (!validationResult.emitSkipped) {
198 const zipped = (0, spec_1.writeAssembly)(this.projectInfo.projectRoot, _fingerprint(assembly), {
199 compress: this.compressAssembly ?? false,
200 });
201 LOG.trace(`${zipped ? 'Zipping' : 'Emitting'} assembly: ${chalk.blue(path.join(this.projectInfo.projectRoot, spec_1.SPEC_FILE_NAME))}`);
202 }
203 try {
204 return {
205 diagnostics: [...this._diagnostics, ...validationResult.diagnostics],
206 emitSkipped: validationResult.emitSkipped,
207 };
208 }
209 finally {
210 this._afterEmit();
211 }
212 function _loadReadme() {
213 // Search for `README.md` in a case-insensitive way
214 const fileName = fs
215 .readdirSync(this.projectInfo.projectRoot)
216 .find((file) => file.toLocaleLowerCase() === 'readme.md');
217 if (fileName == null) {
218 return undefined;
219 }
220 const readmePath = path.join(this.projectInfo.projectRoot, fileName);
221 return loadAndRenderReadme(readmePath, this.projectInfo.projectRoot);
222 }
223 function _loadDocs() {
224 if (!this.projectInfo.stability && !this.projectInfo.deprecated) {
225 return undefined;
226 }
227 const deprecated = this.projectInfo.deprecated;
228 const stability = this.projectInfo.stability;
229 return { deprecated, stability };
230 }
231 }
232 _afterEmit() {
233 this._diagnostics = [];
234 this._deferred = [];
235 this._types.clear();
236 this._submoduleMap.clear();
237 this._submodules.clear();
238 this._packageInfoCache.clear();
239 }
240 /**
241 * Defer a callback until a (set of) types are available
242 *
243 * This is a helper function around _defer() which encapsulates the _dereference
244 * action (which is basically the majority use case for _defer anyway).
245 *
246 * Will not invoke the function with any 'undefined's; an error will already have been emitted in
247 * that case anyway.
248 *
249 * @param fqn FQN of the current type (the type that has a dependency on baseTypes)
250 * @param baseTypes Array of type references to be looked up
251 * @param referencingNode Node to report a diagnostic on if we fail to look up a t ype
252 * @param cb Callback to be invoked with the Types corresponding to the TypeReferences in baseTypes
253 */
254 _deferUntilTypesAvailable(fqn, baseTypes, referencingNode, cb) {
255 // We can do this one eagerly
256 if (baseTypes.length === 0) {
257 cb();
258 return;
259 }
260 const baseFqns = baseTypes.map((bt) => (typeof bt === 'string' ? bt : bt.fqn));
261 this._defer(fqn, baseFqns, () => {
262 const resolved = baseFqns.map((x) => this._dereference(x, referencingNode)).filter((x) => x !== undefined);
263 if (resolved.length > 0) {
264 cb(...resolved);
265 }
266 });
267 }
268 /**
269 * Defer checks for after the program has been entirely processed; useful for verifying type references that may not
270 * have been discovered yet, and verifying properties about them.
271 *
272 * The callback is guaranteed to be executed only after all deferreds for all types in 'dependedFqns' have
273 * been executed.
274 *
275 * @param fqn FQN of the current type.
276 * @param dependedFqns List of FQNs of types this callback depends on. All deferreds for all
277 * @param cb the function to be called in a deferred way. It will be bound with ``this``, so it can depend on using
278 * ``this``.
279 */
280 _defer(fqn, dependedFqns, cb) {
281 this._deferred.push({ fqn, dependedFqns, cb: cb.bind(this) });
282 }
283 /**
284 * Obtains the ``spec.Type`` for a given ``spec.NamedTypeReference``.
285 *
286 * @param ref the type reference to be de-referenced
287 *
288 * @returns the de-referenced type, if it was found, otherwise ``undefined``.
289 */
290 _dereference(ref, referencingNode) {
291 if (typeof ref !== 'string') {
292 ref = ref.fqn;
293 }
294 const [assm] = ref.split('.');
295 let type;
296 if (assm === this.projectInfo.name) {
297 type = this._types.get(ref);
298 }
299 else {
300 const assembly = this.projectInfo.dependencyClosure.find((dep) => dep.name === assm);
301 type = assembly?.types?.[ref];
302 // since we are exposing a type of this assembly in this module's public API,
303 // we expect it to appear as a peer dependency instead of a normal dependency.
304 if (assembly) {
305 if (!(assembly.name in this.projectInfo.peerDependencies)) {
306 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_0005_MISSING_PEER_DEPENDENCY.create(referencingNode, // Cheating here for now, until the referencingNode can be made required
307 assembly.name, ref));
308 }
309 }
310 }
311 if (!type) {
312 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_9002_UNRESOLVEABLE_TYPE.create(referencingNode, // Cheating here for now, until the referencingNode can be made required
313 ref));
314 }
315 return type;
316 }
317 /**
318 * Compute the JSII fully qualified name corresponding to a ``ts.Type`` instance. If for any reason a name cannot be
319 * computed for the type, a marker is returned instead, and an ``ts.DiagnosticCategory.Error`` diagnostic is
320 * inserted in the assembler context.
321 *
322 * @param type the type for which a JSII fully qualified name is needed.
323 * @param typeAnnotationNode the type annotation for which this FQN is generated. This is used for attaching the error
324 * marker. When there is no explicit type annotation (e.g: inferred method return type), the
325 * preferred substitute is the "type-inferred" element's name.
326 * @param typeUse the reason why this type was resolved (e.g: "return type")
327 * @param isThisType whether this type was specified or inferred as "this" or not
328 *
329 * @returns the FQN of the type, or some "unknown" marker.
330 */
331 _getFQN(type, typeAnnotationNode, typeUse, isThisType) {
332 const sym = symbolFromType(type, this._typeChecker);
333 const typeDeclaration = sym.valueDeclaration ?? sym.declarations?.[0];
334 // Set to true to prevent further adding of Error diagnostics for known-bad reference
335 let hasError = false;
336 if (this._isPrivateOrInternal(sym)) {
337 // Check if this type is "this" (explicit or inferred method return type).
338 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3001_EXPOSED_INTERNAL_TYPE.create(typeAnnotationNode, sym, isThisType, typeUse).addRelatedInformationIf(typeDeclaration, 'The referenced type is declared here'));
339 hasError = true;
340 }
341 const tsName = this._typeChecker.getFullyQualifiedName(sym);
342 const groups = /^"([^"]+)"\.(.*)$/.exec(tsName);
343 if (!groups) {
344 if (!hasError) {
345 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3001_EXPOSED_INTERNAL_TYPE.create(typeAnnotationNode, sym, isThisType, typeUse).addRelatedInformationIf(typeDeclaration, 'The referenced type is declared here'));
346 hasError = true;
347 }
348 return tsName;
349 }
350 const [, modulePath, typeName] = groups;
351 const pkg = this.findPackageInfo(modulePath);
352 if (!pkg) {
353 if (!hasError) {
354 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_9003_UNRESOLVEABLE_MODULE.create(typeAnnotationNode, modulePath).addRelatedInformationIf(typeDeclaration, 'The referenced type is declared here'));
355 hasError = true;
356 }
357 return `unknown.${typeName}`;
358 }
359 // If the symbol comes from an assembly whose submodules we've already
360 // spidered (or from the current assembly), look up there. This relies
361 // on an entry-point import of the library having been done first
362 // (`import * as x from 'module-root';`)
363 const submodule = this._submoduleMap.get(sym);
364 if (submodule != null) {
365 const submoduleNs = this._submodules.get(submodule).fqnResolutionPrefix;
366 return `${submoduleNs}.${typeName}`;
367 }
368 // This is the fallback: in case we can't find a symbolId for the given
369 // type, we're return this value. This is for backwards compatibility with
370 // modules that haven't been compiled to have symbolId support. Those also
371 // most likely won't be using submodules so this legacy guess will be correct.
372 const fallbackFqn = `${pkg.name}.${typeName}`;
373 // If the type is coming from the current module, we won't find it in a dependency
374 if (pkg.name === this.projectInfo.name) {
375 return fallbackFqn;
376 }
377 // Otherwise look up the symbol identifier in the dependency assemblies
378 // This is now the preferred mechanism but we can't do this as the only mechanism,
379 // as we may still have compile against very old assemblies that don't have a
380 // symbol identifier table at all.
381 const dep = this.projectInfo.dependencyClosure.find((d) => d.name === pkg.name);
382 if (!dep) {
383 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_9000_UNKNOWN_MODULE.create(typeAnnotationNode, pkg.name));
384 return fallbackFqn;
385 }
386 const symbolId = (0, symbol_id_1.symbolIdentifier)(this._typeChecker, sym, {
387 assembly: dep,
388 });
389 const fqn = (dep && symbolId ? symbolIdIndex(dep)[symbolId] : undefined) ?? fallbackFqn;
390 if (!fqn || !this._dereference({ fqn }, sym.valueDeclaration)) {
391 if (!hasError) {
392 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3002_USE_OF_UNEXPORTED_FOREIGN_TYPE.create(typeAnnotationNode, fqn ?? tsName, typeUse, pkg).addRelatedInformationIf(typeDeclaration, 'The referenced type is declared here'));
393 hasError = true;
394 }
395 }
396 return fqn;
397 }
398 /**
399 * For all modules in the dependency closure, crawl their exports to register
400 * the submodules they contain.
401 *
402 * @param entryPoint the main source file for the currently compiled module.
403 */
404 _registerDependenciesNamespaces(entryPoint) {
405 for (const assm of this.projectInfo.dependencyClosure) {
406 const resolved = ts.resolveModuleName(assm.name, entryPoint.fileName, this.program.getCompilerOptions(), ts.sys);
407 // If we can't resolve the module name, simply ignore it (TypeScript compilation likely failed)
408 if (resolved.resolvedModule == null) {
409 continue;
410 }
411 const source = this.program.getSourceFile(resolved.resolvedModule.resolvedFileName);
412 const depMod = source && this._typeChecker.getSymbolAtLocation(source);
413 // It's unlikely, but if we can't get the SourceFile here, ignore it (TypeScript compilation probably failed)
414 if (depMod == null) {
415 continue;
416 }
417 const depRoot = packageRoot(resolved.resolvedModule.resolvedFileName);
418 for (const symbol of this._typeChecker.getExportsOfModule(depMod)) {
419 this._registerNamespaces(symbol, depRoot);
420 }
421 }
422 function packageRoot(file) {
423 const parent = path.dirname(file);
424 if (path.basename(parent) === 'node_modules' || parent === file) {
425 return file;
426 }
427 return packageRoot(parent);
428 }
429 }
430 _registerNamespaces(symbol, packageRoot) {
431 const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
432 if (declaration == null) {
433 // Nothing to do here...
434 return;
435 }
436 if (ts.isModuleDeclaration(declaration)) {
437 // Looks like:
438 //
439 // export some_namespace {
440 // ...
441 // }
442 //
443 // No way to configure targets
444 const { fqn, fqnResolutionPrefix } = qualifiedNameOf.call(this, symbol, true);
445 this._submodules.set(symbol, {
446 fqn,
447 fqnResolutionPrefix,
448 symbolId: (0, symbol_id_1.symbolIdentifier)(this._typeChecker, symbol),
449 locationInModule: this.declarationLocation(declaration),
450 });
451 this._addToSubmodule(symbol, symbol, packageRoot);
452 return;
453 }
454 if (!ts.isNamespaceExport(declaration)) {
455 // Nothing to do here...
456 return;
457 }
458 const moduleSpecifier = declaration.parent.moduleSpecifier;
459 if (moduleSpecifier == null || !ts.isStringLiteral(moduleSpecifier)) {
460 // There is a grammar error here, so we'll let tsc report this for us.
461 return;
462 }
463 const resolution = ts.resolveModuleName(moduleSpecifier.text, declaration.getSourceFile().fileName, this.program.getCompilerOptions(), this.system);
464 if (resolution.resolvedModule == null) {
465 // Unresolvable module... We'll let tsc report this for us.
466 return;
467 }
468 if (
469 // We're not looking into a dependency's namespace exports, and the resolution says it's external
470 (packageRoot === this.projectInfo.projectRoot && resolution.resolvedModule.isExternalLibraryImport) ||
471 // Or the module resolves outside of the current dependency's tree entirely
472 !isUnder(resolution.resolvedModule.resolvedFileName, packageRoot) ||
473 // Or the module is under one the current dependency's node_modules subtree
474 resolution.resolvedModule.resolvedFileName
475 .split('/') // Separator is always '/', even on Windows
476 .filter((entry) => entry === 'node_modules').length !==
477 packageRoot.split('/').filter((entry) => entry === 'node_modules').length) {
478 // External re-exports are "pure-javascript" sugar; they need not be
479 // represented in the jsii Assembly since the types in there will be
480 // resolved through dependencies.
481 return;
482 }
483 const sourceFile = this.program.getSourceFile(resolution.resolvedModule.resolvedFileName);
484 const sourceModule = this._typeChecker.getSymbolAtLocation(sourceFile);
485 // If there's no module, it's a syntax error, and tsc will have reported it for us.
486 if (sourceModule) {
487 if (symbol.name !== Case.camel(symbol.name) && symbol.name !== Case.snake(symbol.name)) {
488 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_8004_SUBMOULE_NAME_CASING.create(declaration.name, symbol.name));
489 }
490 const { fqn, fqnResolutionPrefix } = qualifiedNameOf.call(this, symbol);
491 const targets = loadSubmoduleTargetConfig(sourceFile.fileName);
492 // There is no need to process the README file for submodules that are
493 // external (i.e: from a dependency), as these will not be emitted in the
494 // assembly. That'd be wasted effort, and could fail if the README file
495 // refers to literate examples that are not packaged in the dependency.
496 const readme = packageRoot === this.projectInfo.projectRoot
497 ? loadSubmoduleReadMe(sourceFile.fileName, this.projectInfo.projectRoot)
498 : undefined;
499 this._submodules.set(symbol, {
500 fqn,
501 fqnResolutionPrefix,
502 targets,
503 readme,
504 symbolId: (0, symbol_id_1.symbolIdentifier)(this._typeChecker, symbol),
505 locationInModule: this.declarationLocation(declaration),
506 });
507 this._addToSubmodule(symbol, sourceModule, packageRoot);
508 }
509 function qualifiedNameOf(sym, inlineNamespace = false) {
510 if (this._submoduleMap.has(sym)) {
511 const parent = this._submodules.get(this._submoduleMap.get(sym));
512 const fqn = `${parent.fqn}.${sym.name}`;
513 return {
514 fqn,
515 fqnResolutionPrefix: inlineNamespace ? parent.fqnResolutionPrefix : fqn,
516 };
517 }
518 const symbolLocation = sym.getDeclarations()?.[0]?.getSourceFile()?.fileName;
519 const pkgInfo = symbolLocation ? this.findPackageInfo(symbolLocation) : undefined;
520 const assemblyName = pkgInfo?.name ?? this.projectInfo.name;
521 const fqn = `${assemblyName}.${sym.name}`;
522 return {
523 fqn,
524 fqnResolutionPrefix: inlineNamespace ? this.projectInfo.name : fqn,
525 };
526 }
527 function loadSubmoduleTargetConfig(submoduleMain) {
528 const jsiirc = path.resolve(submoduleMain, '..', '.jsiirc.json');
529 if (!fs.existsSync(jsiirc)) {
530 return undefined;
531 }
532 const data = JSON.parse(fs.readFileSync(jsiirc, 'utf-8'));
533 return data.targets;
534 }
535 /**
536 * Load the README for the given submodule
537 *
538 * If the submodule is loaded from a complete directory (determined by the 'main'
539 * file ending in `index.[d.]ts`, then we load `README.md` in that same directory.
540 *
541 * If the submodule is loaded from a file, like `mymodule.[d.]ts`, we will load
542 * `mymodule.README.md`.
543 */
544 function loadSubmoduleReadMe(submoduleMain, projectRoot) {
545 const fileBase = path.basename(submoduleMain).replace(/(\.d)?\.ts$/, '');
546 const readMeName = fileBase === 'index' ? 'README.md' : `${fileBase}.README.md`;
547 const fullPath = path.join(path.dirname(submoduleMain), readMeName);
548 return loadAndRenderReadme(fullPath, projectRoot);
549 }
550 }
551 /**
552 * Registers Symbols to a particular submodule. This is used to associate
553 * declarations exported by an `export * as ns from 'moduleLike';` statement
554 * so that they can subsequently be correctly namespaced.
555 *
556 * @param ns the symbol that identifies the submodule.
557 * @param moduleLike the module-like symbol bound to the submodule.
558 * @param packageRoot the root of the package being traversed.
559 */
560 _addToSubmodule(ns, moduleLike, packageRoot) {
561 // For each symbol exported by the moduleLike, map it to the ns submodule.
562 for (const symbol of this._typeChecker.getExportsOfModule(moduleLike)) {
563 if (this._submoduleMap.has(symbol)) {
564 const currNs = this._submoduleMap.get(symbol);
565 // Checking if there's been two submodules exporting the same symbol,
566 // which is illegal. We can tell if the currently registered symbol has
567 // a different name than the one we're currently trying to register in.
568 if (currNs.name !== ns.name) {
569 const currNsDecl = currNs.valueDeclaration ?? currNs.declarations?.[0];
570 const nsDecl = ns.valueDeclaration ?? ns.declarations?.[0];
571 // Make sure the error message always lists causes in the same order
572 const refs = [
573 { decl: currNsDecl, name: currNs.name },
574 { decl: nsDecl, name: ns.name },
575 ].sort(({ name: l }, { name: r }) => l.localeCompare(r));
576 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3003_SYMBOL_IS_EXPORTED_TWICE.create(_nameOrDeclarationNode(symbol), refs[0].name, refs[1].name)
577 .addRelatedInformationIf(refs[0].decl, `Symbol is exported under the "${refs[0].name}" submodule`)
578 .addRelatedInformationIf(refs[1].decl, `Symbol is exported under the "${refs[1].name}" submodule`));
579 }
580 // Found two re-exports, which is odd, but they use the same submodule,
581 // so it's probably okay? That's likely a tsc error, which will have
582 // been reported for us already anyway.
583 continue;
584 }
585 this._submoduleMap.set(symbol, ns);
586 // If the exported symbol has any declaration, and that delcaration is of
587 // an entity that can have nested declarations of interest to jsii
588 // (classes, interfaces, enums, modules), we need to also associate those
589 // nested symbols to the submodule (or they won't be named correctly!)
590 const decl = symbol.declarations?.[0];
591 if (decl != null) {
592 if (ts.isClassDeclaration(decl) || ts.isInterfaceDeclaration(decl) || ts.isEnumDeclaration(decl)) {
593 const type = this._typeChecker.getTypeAtLocation(decl);
594 if (isSingleValuedEnum(type, this._typeChecker)) {
595 // type.symbol !== symbol, because symbol is the enum itself, but
596 // since it's single-valued, the TypeChecker will only show us the
597 // value's symbol later on.
598 this._submoduleMap.set(type.symbol, ns);
599 }
600 if (type.symbol.exports) {
601 // eslint-disable-next-line no-await-in-loop
602 this._addToSubmodule(ns, symbol, packageRoot);
603 }
604 }
605 else if (ts.isModuleDeclaration(decl)) {
606 // eslint-disable-next-line no-await-in-loop
607 this._registerNamespaces(symbol, packageRoot);
608 }
609 else if (ts.isNamespaceExport(decl)) {
610 // eslint-disable-next-line no-await-in-loop
611 this._registerNamespaces(symbol, packageRoot);
612 }
613 }
614 }
615 }
616 /**
617 * Register exported types in ``this.types``.
618 *
619 * @param node a node found in a module
620 * @param namePrefix the prefix for the types' namespaces
621 */
622 // eslint-disable-next-line complexity
623 _visitNode(node, context) {
624 if (ts.isNamespaceExport(node)) {
625 // export * as ns from 'module';
626 // Note: the "ts.NamespaceExport" refers to the "export * as ns" part of
627 // the statement only. We must refer to `node.parent` in order to be able
628 // to access the module specifier ("from 'module'") part.
629 const symbol = this._typeChecker.getSymbolAtLocation(node.parent.moduleSpecifier);
630 if (LOG.isTraceEnabled()) {
631 LOG.trace(`Entering submodule: ${chalk.cyan([...context.namespace, symbol.name].join('.'))}`);
632 }
633 const nsContext = context.appendNamespace(node.name.text);
634 const allTypes = this._typeChecker.getExportsOfModule(symbol).flatMap((child) => {
635 const decl = child.declarations?.[0];
636 if (decl == null) {
637 return [];
638 }
639 return this._visitNode(decl, nsContext);
640 });
641 if (LOG.isTraceEnabled()) {
642 LOG.trace(`Leaving submodule: ${chalk.cyan([...context.namespace, symbol.name].join('.'))}`);
643 }
644 return allTypes;
645 }
646 if (ts.isExportSpecifier(node)) {
647 // This is what happens when one does `export { Symbol } from "./location";`
648 // ExportSpecifier: ~~~~~~
649 const resolvedSymbol = this._typeChecker.getExportSpecifierLocalTargetSymbol(node);
650 const decl = resolvedSymbol?.valueDeclaration ?? resolvedSymbol?.declarations?.[0];
651 if (!decl) {
652 // A grammar error, compilation will already have failed
653 return [];
654 }
655 return this._visitNode(decl, context);
656 }
657 if ((ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) === 0) {
658 return [];
659 }
660 let jsiiType;
661 if (ts.isClassDeclaration(node) && _isExported(node)) {
662 // export class Name { ... }
663 this._validateHeritageClauses(node.heritageClauses);
664 jsiiType = this._visitClass(this._typeChecker.getTypeAtLocation(node), context);
665 if (jsiiType) {
666 this.registerExportedClassFqn(node, jsiiType.fqn);
667 }
668 }
669 else if (ts.isInterfaceDeclaration(node) && _isExported(node)) {
670 // export interface Name { ... }
671 this._validateHeritageClauses(node.heritageClauses);
672 jsiiType = this._visitInterface(this._typeChecker.getTypeAtLocation(node), context);
673 }
674 else if (ts.isEnumDeclaration(node) && _isExported(node)) {
675 // export enum Name { ... }
676 jsiiType = this._visitEnum(this._typeChecker.getTypeAtLocation(node), context);
677 }
678 else if (ts.isModuleDeclaration(node)) {
679 // export namespace name { ... }
680 const name = node.name.getText();
681 const symbol = this._typeChecker.getSymbolAtLocation(node.name);
682 if (LOG.isTraceEnabled()) {
683 LOG.trace(`Entering namespace: ${chalk.cyan([...context.namespace, name].join('.'))}`);
684 }
685 const nsContext = context.appendNamespace(node.name.getText());
686 const allTypes = this._typeChecker.getExportsOfModule(symbol).flatMap((prop) => {
687 const decl = prop.declarations?.[0];
688 if (decl == null) {
689 return [];
690 }
691 return this._visitNode(decl, nsContext);
692 });
693 if (LOG.isTraceEnabled()) {
694 LOG.trace(`Leaving namespace: ${chalk.cyan([...context.namespace, name].join('.'))}`);
695 }
696 return allTypes;
697 }
698 else {
699 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(ts.getNameOfDeclaration(node) ?? node, node.kind));
700 }
701 if (!jsiiType) {
702 return [];
703 }
704 // If symbolId hasn't been set yet, set it here
705 if (!jsiiType.symbolId) {
706 jsiiType.symbolId = this.getSymbolId(node);
707 }
708 // Let's quickly verify the declaration does not collide with a submodule. Submodules get case-adjusted for each
709 // target language separately, so names cannot collide with case-variations.
710 for (const submodule of this._submodules.keys()) {
711 const candidates = Array.from(new Set([submodule.name, Case.camel(submodule.name), Case.pascal(submodule.name), Case.snake(submodule.name)]));
712 const colliding = candidates.find((name) => `${this.projectInfo.name}.${name}` === jsiiType.fqn);
713 if (colliding != null) {
714 const submoduleDeclName = _nameOrDeclarationNode(submodule);
715 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_5011_SUBMODULE_NAME_CONFLICT.create(ts.getNameOfDeclaration(node) ?? node, submodule.name, jsiiType.name, candidates).addRelatedInformationIf(submoduleDeclName, 'This is the conflicting submodule declaration'));
716 }
717 }
718 if (LOG.isInfoEnabled()) {
719 LOG.info(`Registering JSII ${chalk.magenta(jsiiType.kind)}: ${chalk.green(jsiiType.fqn)}`);
720 }
721 this._types.set(jsiiType.fqn, jsiiType);
722 jsiiType.locationInModule = this.declarationLocation(node);
723 const type = this._typeChecker.getTypeAtLocation(node);
724 if (type.symbol.exports) {
725 const nestedContext = context.appendNamespace(type.symbol.name);
726 const visitedNodes = this._typeChecker
727 .getExportsOfModule(type.symbol)
728 .filter((s) => s.declarations)
729 .flatMap((exportedNode) => {
730 const decl = exportedNode.valueDeclaration ?? exportedNode.declarations?.[0];
731 if (decl == null) {
732 return [];
733 }
734 return [this._visitNode(decl, nestedContext)];
735 });
736 for (const nestedTypes of visitedNodes) {
737 for (const nestedType of nestedTypes) {
738 if (nestedType.namespace !== nestedContext.namespace.join('.')) {
739 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_5012_NAMESPACE_IN_TYPE.create(ts.getNameOfDeclaration(node) ?? node, jsiiType.fqn, nestedType.namespace));
740 }
741 }
742 }
743 }
744 return [jsiiType];
745 }
746 getSymbolId(node) {
747 return (0, symbol_id_1.symbolIdentifier)(this._typeChecker, this._typeChecker.getTypeAtLocation(node).symbol);
748 }
749 _validateHeritageClauses(clauses) {
750 if (clauses == null || clauses.length === 0) {
751 // Nothing to do.
752 return;
753 }
754 for (const clause of clauses) {
755 for (const node of clause.types) {
756 const parentType = this._typeChecker.getTypeAtLocation(node);
757 if (parentType.symbol == null) {
758 // The parent type won't have a symbol if it's an "error type" inserted by the type checker when the original
759 // code contains a compilation error. In such cases, the TypeScript compiler will already have reported about
760 // the incoherent declarations, so we'll just not re-validate it there (we'd fail anyway).
761 continue;
762 }
763 // For some reason, we cannot trust parentType.isClassOrInterface()
764 const badDecl = parentType.symbol.declarations?.find((decl) => !ts.isClassDeclaration(decl) && // <-- local classes
765 !ts.isInterfaceDeclaration(decl) && // <-- local interfaces
766 !ts.isModuleDeclaration(decl));
767 if (badDecl != null) {
768 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3004_INVALID_SUPERTYPE.create(node, clause, badDecl).addRelatedInformation(badDecl, 'The invalid super type is declared here.'));
769 }
770 }
771 }
772 }
773 declarationLocation(node) {
774 const file = node.getSourceFile();
775 const line = ts.getLineAndCharacterOfPosition(file, node.getStart()).line;
776 const filename = path.normalize(path.relative(this.projectInfo.projectRoot, file.fileName)).replace(/\\/g, '/');
777 return {
778 filename,
779 line: line + 1,
780 };
781 }
782 _processBaseInterfaces(fqn, baseTypes) {
783 const erasedBases = new Array();
784 if (!baseTypes) {
785 return { erasedBases };
786 }
787 const result = new Array();
788 const baseInterfaces = new Set();
789 const processBaseTypes = (types) => {
790 for (const iface of types) {
791 // base is private/internal, so we continue recursively with it's own bases
792 if (this._isPrivateOrInternal(iface.symbol) || isInternalSymbol(iface.symbol)) {
793 erasedBases.push(iface);
794 if (!isInternalSymbol(iface.symbol)) {
795 const bases = iface.getBaseTypes();
796 if (bases) {
797 processBaseTypes(bases);
798 }
799 }
800 continue;
801 }
802 baseInterfaces.add(iface);
803 }
804 };
805 processBaseTypes(baseTypes);
806 const typeRefs = Array.from(baseInterfaces).map((iface) => {
807 const decl = iface.symbol.valueDeclaration;
808 const typeRef = this._typeReference(iface, decl, 'base interface');
809 return { decl, typeRef };
810 });
811 for (const { decl, typeRef } of typeRefs) {
812 if (!spec.isNamedTypeReference(typeRef)) {
813 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3005_TYPE_USED_AS_INTERFACE.create(decl, typeRef));
814 continue;
815 }
816 this._deferUntilTypesAvailable(fqn, [typeRef], decl, (deref) => {
817 if (!spec.isInterfaceType(deref)) {
818 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3005_TYPE_USED_AS_INTERFACE.create(decl, typeRef));
819 }
820 });
821 result.push(typeRef);
822 }
823 return {
824 interfaces: result.length === 0 ? undefined : result,
825 erasedBases,
826 };
827 }
828 // eslint-disable-next-line complexity
829 _visitClass(type, ctx) {
830 if (LOG.isTraceEnabled()) {
831 LOG.trace(`Processing class: ${chalk.gray(ctx.namespace.join('.'))}.${chalk.cyan(type.symbol.name)}`);
832 }
833 if (_hasInternalJsDocTag(type.symbol)) {
834 return undefined;
835 }
836 this._warnAboutReservedWords(type.symbol);
837 const fqn = `${[this.projectInfo.name, ...ctx.namespace].join('.')}.${type.symbol.name}`;
838 if (Case.pascal(type.symbol.name) !== type.symbol.name) {
839 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_8000_PASCAL_CASED_TYPE_NAMES.create(type.symbol.valueDeclaration.name ??
840 type.symbol.valueDeclaration ??
841 type.symbol.declarations?.[0], type.symbol.name));
842 }
843 const classDeclaration = type.symbol.valueDeclaration;
844 for (const typeParam of classDeclaration.typeParameters ?? []) {
845 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_1006_GENERIC_TYPE.create(typeParam));
846 }
847 const jsiiType = bindings.setClassRelatedNode({
848 assembly: this.projectInfo.name,
849 fqn,
850 kind: spec.TypeKind.Class,
851 name: type.symbol.name,
852 namespace: ctx.namespace.length > 0 ? ctx.namespace.join('.') : undefined,
853 docs: this._visitDocumentation(type.symbol, ctx).docs,
854 }, classDeclaration);
855 if (_isAbstract(type.symbol, jsiiType)) {
856 jsiiType.abstract = true;
857 }
858 const erasedBases = new Array();
859 for (let base of type.getBaseTypes() ?? []) {
860 if (jsiiType.base) {
861 // Ignoring this - there has already been a compilation error generated by tsc here.
862 continue;
863 }
864 //
865 // base classes ("extends foo")
866 // Crawl up the inheritance tree if the current base type is not exported, so we identify the type(s) to be
867 // erased, and identify the closest exported base class, should there be one.
868 while (base && this._isPrivateOrInternal(base.symbol)) {
869 LOG.debug(`Base class of ${chalk.green(jsiiType.fqn)} named ${chalk.green(base.symbol.name)} is not exported, erasing it...`);
870 erasedBases.push(base);
871 base = (base.getBaseTypes() ?? [])[0];
872 }
873 if (!base || isInternalSymbol(base.symbol)) {
874 // There is no exported base class to be found, pretend this class has no base class.
875 continue;
876 }
877 // eslint-disable-next-line no-await-in-loop
878 const ref = this._typeReference(base, type.symbol.valueDeclaration ?? type.symbol.declarations?.[0], 'base class');
879 if (!spec.isNamedTypeReference(ref)) {
880 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3006_TYPE_USED_AS_CLASS.create(base.symbol.valueDeclaration ?? base.symbol.declarations?.[0], ref));
881 continue;
882 }
883 this._deferUntilTypesAvailable(fqn, [ref], base.symbol.valueDeclaration, (deref) => {
884 if (!spec.isClassType(deref)) {
885 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3006_TYPE_USED_AS_CLASS.create(base.symbol.valueDeclaration ?? base.symbol.declarations?.[0], ref));
886 }
887 });
888 jsiiType.base = ref.fqn;
889 }
890 //
891 // base interfaces ("implements foo")
892 // collect all "implements" declarations from the current type and all
893 // erased base types (because otherwise we lose them, see jsii#487)
894 const implementsClauses = new Array();
895 for (const heritage of [type, ...erasedBases].map((t) => t.symbol.valueDeclaration.heritageClauses ?? [])) {
896 for (const clause of heritage) {
897 if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
898 // Handled by `getBaseTypes`
899 continue;
900 }
901 else if (clause.token !== ts.SyntaxKind.ImplementsKeyword) {
902 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(clause, `Ignoring ${ts.SyntaxKind[clause.token]} heritage clause`));
903 continue;
904 }
905 implementsClauses.push(clause);
906 }
907 }
908 // process all "implements" clauses
909 const allInterfaces = new Set();
910 const baseInterfaces = implementsClauses.map((clause) => this._processBaseInterfaces(fqn, clause.types.map((t) => this._getTypeFromTypeNode(t))));
911 for (const { interfaces } of baseInterfaces) {
912 for (const ifc of interfaces ?? []) {
913 allInterfaces.add(ifc.fqn);
914 }
915 if (interfaces) {
916 this._deferUntilTypesAvailable(jsiiType.fqn, interfaces, type.symbol.valueDeclaration, (...ifaces) => {
917 for (const iface of ifaces) {
918 if (spec.isInterfaceType(iface) && iface.datatype) {
919 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3007_ILLEGAL_STRUCT_EXTENSION.create(type.symbol.valueDeclaration ?? type.symbol.declarations?.[0], jsiiType, iface));
920 }
921 }
922 });
923 }
924 }
925 if (allInterfaces.size > 0) {
926 jsiiType.interfaces = Array.from(allInterfaces);
927 }
928 if (!type.isClass()) {
929 throw new Error('Oh no');
930 }
931 const allDeclarations = (type.symbol.declarations ?? []).map((decl) => ({ decl, type }));
932 // Considering erased bases' declarations, too, so they are "blended in"
933 for (const base of erasedBases) {
934 allDeclarations.push(...(base.symbol.declarations ?? []).map((decl) => ({
935 decl,
936 type: base,
937 })));
938 }
939 for (const { decl, type: declaringType } of allDeclarations) {
940 const classDecl = decl;
941 if (!classDecl.members) {
942 continue;
943 }
944 for (const memberDecl of classDecl.members) {
945 if (ts.isSemicolonClassElement(memberDecl)) {
946 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_9996_UNNECESSARY_TOKEN.create(memberDecl));
947 continue;
948 }
949 const member = ts.isConstructorDeclaration(memberDecl)
950 ? getConstructor(this._typeChecker.getTypeAtLocation(memberDecl.parent))
951 : ts.isIndexSignatureDeclaration(memberDecl)
952 ? type.symbol.members?.get(ts.InternalSymbolName.Index) ??
953 type.symbol.exports?.get(ts.InternalSymbolName.Index)
954 : this._typeChecker.getSymbolAtLocation(ts.getNameOfDeclaration(memberDecl) ?? memberDecl);
955 if (member && this._isPrivateOrInternal(member, memberDecl)) {
956 continue;
957 }
958 if (ts.isIndexSignatureDeclaration(memberDecl)) {
959 // Index signatures (static or not) are not supported in the jsii type model.
960 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_1999_UNSUPPORTED.create(memberDecl, {
961 what: 'Index signatures',
962 suggestInternal: true,
963 }));
964 continue;
965 }
966 if (!(declaringType.symbol.getDeclarations() ?? []).find((d) => d === memberDecl.parent)) {
967 continue;
968 }
969 // constructors are handled later
970 if (ts.isConstructorDeclaration(memberDecl)) {
971 continue;
972 }
973 // eslint-disable-next-line no-await-in-loop
974 if (ts.isMethodDeclaration(memberDecl) || ts.isMethodSignature(memberDecl)) {
975 // eslint-disable-next-line no-await-in-loop
976 this._visitMethod(member, jsiiType, ctx.replaceStability(jsiiType.docs?.stability), classDecl);
977 }
978 else if (ts.isPropertyDeclaration(memberDecl) ||
979 ts.isPropertySignature(memberDecl) ||
980 ts.isAccessor(memberDecl)) {
981 // eslint-disable-next-line no-await-in-loop
982 this._visitProperty(member, jsiiType, ctx.replaceStability(jsiiType.docs?.stability), classDecl);
983 }
984 else {
985 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(ts.getNameOfDeclaration(memberDecl) ?? memberDecl, memberDecl.kind));
986 }
987 /* eslint-enable no-await-in-loop */
988 }
989 }
990 const memberEmitContext = ctx.replaceStability(jsiiType.docs && jsiiType.docs.stability);
991 // Find the first defined constructor in this class, or it's erased bases
992 const constructor = [type, ...erasedBases].map(getConstructor).find((ctor) => ctor != null);
993 const ctorDeclaration = constructor && constructor.declarations?.[0];
994 if (constructor && ctorDeclaration) {
995 const signature = this._typeChecker.getSignatureFromDeclaration(ctorDeclaration);
996 if ((ts.getCombinedModifierFlags(ctorDeclaration) & ts.ModifierFlags.Private) === 0) {
997 jsiiType.initializer = {
998 locationInModule: this.declarationLocation(ctorDeclaration),
999 };
1000 if (signature) {
1001 for (const param of signature.getParameters()) {
1002 jsiiType.initializer.parameters = jsiiType.initializer.parameters ?? [];
1003 jsiiType.initializer.parameters.push(
1004 // eslint-disable-next-line no-await-in-loop
1005 this._toParameter(param, ctx.replaceStability(jsiiType.docs?.stability)));
1006 jsiiType.initializer.variadic = jsiiType.initializer?.parameters?.some((p) => !!p.variadic) || undefined;
1007 jsiiType.initializer.protected =
1008 (ts.getCombinedModifierFlags(ctorDeclaration) & ts.ModifierFlags.Protected) !== 0 || undefined;
1009 }
1010 }
1011 this._verifyConsecutiveOptionals(ctorDeclaration, jsiiType.initializer.parameters);
1012 jsiiType.initializer.docs = this._visitDocumentation(constructor, memberEmitContext).docs;
1013 }
1014 // Process constructor-based property declarations even if constructor is private
1015 if (signature) {
1016 for (const param of signature.getParameters()) {
1017 const decl = param.valueDeclaration ?? param.declarations?.[0];
1018 if (decl && ts.isParameterPropertyDeclaration(decl, decl.parent) && !this._isPrivateOrInternal(param)) {
1019 // eslint-disable-next-line no-await-in-loop
1020 this._visitProperty(param, jsiiType, memberEmitContext, ctorDeclaration.parent);
1021 }
1022 }
1023 }
1024 }
1025 else if (jsiiType.base) {
1026 this._deferUntilTypesAvailable(fqn, [jsiiType.base], type.symbol.valueDeclaration, (baseType) => {
1027 if (spec.isClassType(baseType)) {
1028 jsiiType.initializer = baseType.initializer;
1029 }
1030 else {
1031 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3999_INCOHERENT_TYPE_MODEL.create(type.symbol.valueDeclaration ?? type.symbol.declarations?.[0], `Base type of ${jsiiType.fqn} (${jsiiType.base}) is not a class`));
1032 }
1033 });
1034 }
1035 else {
1036 jsiiType.initializer = {
1037 docs: ctx.stability && { stability: ctx.stability },
1038 };
1039 }
1040 this._verifyNoStaticMixing(jsiiType, type.symbol.valueDeclaration ?? type.symbol.declarations?.[0]);
1041 return _sortMembers(jsiiType);
1042 }
1043 /**
1044 * Use the TypeChecker's getTypeFromTypeNode, but throw a descriptive error if it fails
1045 */
1046 _getTypeFromTypeNode(t) {
1047 const type = this._typeChecker.getTypeFromTypeNode(t);
1048 if (isErrorType(type)) {
1049 throw new Error(`Unable to resolve type: ${t.getFullText()}. This typically happens if something is wrong with your dependency closure.`);
1050 }
1051 return type;
1052 }
1053 /**
1054 * Check that this class doesn't declare any members that are of different staticness in itself or any of its bases
1055 */
1056 _verifyNoStaticMixing(klass, decl) {
1057 // Check class itself--may have two methods/props with the same name, so check the arrays
1058 const statics = new Set((klass.methods ?? [])
1059 .concat(klass.properties ?? [])
1060 .filter((x) => x.static)
1061 .map((x) => x.name));
1062 const nonStatics = new Set((klass.methods ?? [])
1063 .concat(klass.properties ?? [])
1064 .filter((x) => !x.static)
1065 .map((x) => x.name));
1066 // Intersect
1067 for (const member of intersect(statics, nonStatics)) {
1068 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_5013_STATIC_INSTANCE_CONFLICT.create(decl, member, klass));
1069 }
1070 // Check against base classes. They will not contain duplicate member names so we can load
1071 // the members into a map.
1072 const classMembers = typeMembers(klass);
1073 this._withBaseClass(klass, decl, (base, recurse) => {
1074 for (const [name, baseMember] of Object.entries(typeMembers(base))) {
1075 const member = classMembers[name];
1076 if (!member) {
1077 continue;
1078 }
1079 if (!!baseMember.static !== !!member.static) {
1080 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_5014_INHERITED_STATIC_CONFLICT.create(decl, member, klass, baseMember, base));
1081 }
1082 }
1083 recurse();
1084 });
1085 }
1086 /**
1087 * Wrapper around _deferUntilTypesAvailable, invoke the callback with the given classes' base type
1088 *
1089 * Does nothing if the given class doesn't have a base class.
1090 *
1091 * The second argument will be a `recurse` function for easy recursion up the inheritance tree
1092 * (no messing around with binding 'self' and 'this' and doing multiple calls to _withBaseClass.)
1093 */
1094 _withBaseClass(klass, decl, cb) {
1095 if (klass.base) {
1096 this._deferUntilTypesAvailable(klass.fqn, [klass.base], decl, (base) => {
1097 if (!spec.isClassType(base)) {
1098 throw new Error('Oh no');
1099 }
1100 cb(base, () => this._withBaseClass(base, decl, cb));
1101 });
1102 }
1103 }
1104 /**
1105 * @returns true if this member is internal and should be omitted from the type manifest
1106 */
1107 _isPrivateOrInternal(symbol, validateDeclaration) {
1108 const hasInternalJsDocTag = _hasInternalJsDocTag(symbol);
1109 const hasInternalSymbolName = isInternalSymbol(symbol);
1110 const hasUnderscorePrefix = !hasInternalSymbolName && symbol.name.startsWith('_');
1111 if (_isPrivate(symbol)) {
1112 LOG.trace(`${chalk.cyan(symbol.name)} is marked "private", or is an unexported type declaration`);
1113 return true;
1114 }
1115 // If all the declarations are marked with `@jsii ignore`, then this is effetcively private as far as jsii is concerned.
1116 if (symbol.declarations?.every((decl) => directives_1.Directives.of(decl, (diag) => this._diagnostics.push(diag)).ignore != null)) {
1117 return true;
1118 }
1119 if (!hasInternalJsDocTag && !hasUnderscorePrefix) {
1120 return false;
1121 }
1122 // We only validate if we have a declaration and the symbol doesn't have an internal name.
1123 if (validateDeclaration && !hasInternalSymbolName) {
1124 if (!hasUnderscorePrefix) {
1125 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_8005_INTERNAL_UNDERSCORE.create(ts.getNameOfDeclaration(validateDeclaration) ?? validateDeclaration, symbol.name));
1126 }
1127 if (!hasInternalJsDocTag) {
1128 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_8006_UNDERSCORE_INTERNAL.create(ts.getNameOfDeclaration(validateDeclaration) ?? validateDeclaration, symbol.name));
1129 }
1130 }
1131 return true;
1132 }
1133 _visitEnum(type, ctx) {
1134 if (LOG.isTraceEnabled()) {
1135 LOG.trace(`Processing enum: ${chalk.gray(ctx.namespace.join('.'))}.${chalk.cyan(type.symbol.name)}`);
1136 }
1137 // Forcefully resolving to the EnumDeclaration symbol for single-valued enums
1138 let decl = type.symbol.declarations?.[0];
1139 let symbol;
1140 if (decl && ts.isEnumMember(decl)) {
1141 decl = decl?.parent;
1142 }
1143 if (decl && ts.isEnumDeclaration(decl)) {
1144 symbol = getSymbolFromDeclaration(decl, this._typeChecker);
1145 }
1146 if (!decl || !symbol || !ts.isEnumDeclaration(decl)) {
1147 throw new Error(`Unable to resolve enum declaration for ${type.symbol.name}!`);
1148 }
1149 if (_hasInternalJsDocTag(symbol)) {
1150 return undefined;
1151 }
1152 // check the enum to see if there are duplicate enum values
1153 this.assertNoDuplicateEnumValues(decl);
1154 this._warnAboutReservedWords(symbol);
1155 const flags = ts.getCombinedModifierFlags(decl);
1156 if (flags & ts.ModifierFlags.Const) {
1157 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_1000_NO_CONST_ENUM.create(decl.modifiers?.find((mod) => mod.kind === ts.SyntaxKind.ConstKeyword) ?? decl));
1158 }
1159 const { docs } = this._visitDocumentation(symbol, ctx);
1160 const typeContext = ctx.replaceStability(docs?.stability);
1161 const members = type.isUnion() ? type.types : [type];
1162 if (Case.pascal(symbol.name) !== symbol.name) {
1163 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_8000_PASCAL_CASED_TYPE_NAMES.create(symbol.valueDeclaration.name, symbol.name));
1164 }
1165 const jsiiType = bindings.setEnumRelatedNode({
1166 assembly: this.projectInfo.name,
1167 fqn: `${[this.projectInfo.name, ...ctx.namespace].join('.')}.${symbol.name}`,
1168 kind: spec.TypeKind.Enum,
1169 members: members.map((m) => {
1170 // eslint-disable-next-line @typescript-eslint/no-shadow
1171 const { docs } = this._visitDocumentation(m.symbol, typeContext);
1172 return { name: m.symbol.name, docs };
1173 }),
1174 name: symbol.name,
1175 namespace: ctx.namespace.length > 0 ? ctx.namespace.join('.') : undefined,
1176 docs,
1177 // Set SymbolId here instead of later, as by default TS will pick single-enum members
1178 // as the target symbol if possible.
1179 symbolId: (0, symbol_id_1.symbolIdentifier)(this._typeChecker, symbol),
1180 }, decl);
1181 return jsiiType;
1182 }
1183 assertNoDuplicateEnumValues(decl) {
1184 const enumValues = decl.members
1185 .filter((m) => m.initializer)
1186 .map((member) => {
1187 return {
1188 value: member.initializer.getText(),
1189 name: member.name.getText(),
1190 decl: ts.getNameOfDeclaration(member),
1191 };
1192 });
1193 const hasDuplicateEnumValues = enumValues.some((val, _, arr) => arr.filter((e) => val.value === e.value).length > 1);
1194 if (hasDuplicateEnumValues) {
1195 const enumValueMap = enumValues.reduce((acc, val) => {
1196 if (!acc[val.value]) {
1197 acc[val.value] = [];
1198 }
1199 acc[val.value].push(val);
1200 return acc;
1201 }, {});
1202 for (const duplicateValue of Object.keys(enumValueMap)) {
1203 if (enumValueMap[duplicateValue].length > 1) {
1204 const err = jsii_diagnostic_1.JsiiDiagnostic.JSII_1004_DUPLICATE_ENUM_VALUE.create(enumValueMap[duplicateValue][0].decl, duplicateValue, enumValueMap[duplicateValue].map((v) => v.name));
1205 for (let i = 1; i < enumValueMap[duplicateValue].length; i++) {
1206 err.addRelatedInformation(enumValueMap[duplicateValue][i].decl, 'The conflicting declaration is here');
1207 }
1208 this._diagnostics.push(err);
1209 }
1210 }
1211 }
1212 }
1213 /**
1214 * Return docs for a symbol
1215 */
1216 _visitDocumentation(sym, context) {
1217 const result = (0, docs_1.parseSymbolDocumentation)(sym, this._typeChecker);
1218 for (const diag of result.diagnostics ?? []) {
1219 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_7999_DOCUMENTATION_ERROR.create(sym.valueDeclaration ?? sym.declarations?.[0], diag));
1220 }
1221 const decl = sym.valueDeclaration ?? sym.declarations?.[0];
1222 // The @struct hint is only valid for interface declarations
1223 if (decl && !ts.isInterfaceDeclaration(decl) && result.hints.struct) {
1224 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_7001_ILLEGAL_HINT.create(_findHint(decl, 'struct'), 'struct', 'interfaces with only readonly properties').addRelatedInformationIf(ts.getNameOfDeclaration(decl) ?? decl, 'The annotated declaration is here'));
1225 // Clean up the bad hint...
1226 delete result.hints.struct;
1227 }
1228 // Apply the current context's stability if none was specified locally.
1229 if (result.docs.stability == null) {
1230 result.docs.stability = context.stability;
1231 }
1232 const allUndefined = Object.values(result.docs).every((v) => v === undefined);
1233 return {
1234 docs: !allUndefined ? result.docs : undefined,
1235 hints: result.hints,
1236 };
1237 }
1238 /**
1239 * Check that all parameters the doc block refers to with a @param declaration actually exist
1240 */
1241 _validateReferencedDocParams(method, methodSym) {
1242 const params = (0, docs_1.getReferencedDocParams)(methodSym);
1243 const actualNames = new Set((method.parameters ?? []).map((p) => p.name));
1244 for (const param of params) {
1245 if (!actualNames.has(param)) {
1246 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_7000_NON_EXISTENT_PARAMETER.create(methodSym.valueDeclaration ?? methodSym.declarations?.[0], method, param));
1247 }
1248 }
1249 }
1250 _visitInterface(type, ctx) {
1251 if (LOG.isTraceEnabled()) {
1252 LOG.trace(`Processing interface: ${chalk.gray(ctx.namespace.join('.'))}.${chalk.cyan(type.symbol.name)}`);
1253 }
1254 if (_hasInternalJsDocTag(type.symbol)) {
1255 return undefined;
1256 }
1257 this._warnAboutReservedWords(type.symbol);
1258 const fqn = `${[this.projectInfo.name, ...ctx.namespace].join('.')}.${type.symbol.name}`;
1259 const { docs, hints } = this._visitDocumentation(type.symbol, ctx);
1260 const jsiiType = bindings.setInterfaceRelatedNode({
1261 assembly: this.projectInfo.name,
1262 fqn,
1263 kind: spec.TypeKind.Interface,
1264 name: type.symbol.name,
1265 namespace: ctx.namespace.length > 0 ? ctx.namespace.join('.') : undefined,
1266 docs,
1267 }, type.symbol.declarations?.[0]);
1268 const { interfaces, erasedBases } = this._processBaseInterfaces(fqn, type.getBaseTypes());
1269 jsiiType.interfaces = apply(interfaces, (arr) => arr.map((i) => i.fqn));
1270 const typeDecl = (type.symbol.valueDeclaration ?? type.symbol.declarations?.[0]);
1271 for (const typeParam of typeDecl?.typeParameters ?? []) {
1272 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_1006_GENERIC_TYPE.create(typeParam));
1273 }
1274 for (const decl of typeDecl?.members?.filter((mem) => ts.isIndexSignatureDeclaration(mem)) ?? []) {
1275 const sym = type.symbol.members?.get(ts.InternalSymbolName.Index) ?? type.symbol.exports?.get(ts.InternalSymbolName.Index);
1276 if (sym != null && this._isPrivateOrInternal(sym, decl)) {
1277 continue;
1278 }
1279 // Index signatures (static or not) are not supported in the jsii type model.
1280 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_1999_UNSUPPORTED.create(decl, { what: 'Index signatures', suggestInternal: true }));
1281 }
1282 for (const declaringType of [type, ...erasedBases]) {
1283 for (const member of declaringType.getProperties()) {
1284 const decl = member.valueDeclaration ?? member.declarations?.[0];
1285 if (!(declaringType.symbol.getDeclarations() ?? []).find((d) => d === decl?.parent)) {
1286 continue;
1287 }
1288 if (this._isPrivateOrInternal(member, decl)) {
1289 continue;
1290 }
1291 if (decl && (ts.isMethodDeclaration(decl) || ts.isMethodSignature(decl))) {
1292 // eslint-disable-next-line no-await-in-loop
1293 this._visitMethod(member, jsiiType, ctx.replaceStability(jsiiType.docs?.stability), typeDecl);
1294 }
1295 else if (decl && (ts.isPropertyDeclaration(decl) || ts.isPropertySignature(decl) || ts.isAccessor(decl))) {
1296 // eslint-disable-next-line no-await-in-loop
1297 this._visitProperty(member, jsiiType, ctx.replaceStability(jsiiType.docs?.stability), typeDecl);
1298 }
1299 else {
1300 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_9998_UNSUPPORTED_NODE.create(_nameOrDeclarationNode(member), (member.valueDeclaration ?? member.declarations?.[0])?.kind ?? ts.SyntaxKind.Unknown));
1301 }
1302 }
1303 }
1304 // Calculate datatype based on the datatypeness of this interface and all of its parents
1305 // To keep the spec minimal the actual values of the attribute are "true" or "undefined" (to represent "false").
1306 const declaration = type.symbol.valueDeclaration ?? type.symbol.declarations?.[0];
1307 this._deferUntilTypesAvailable(fqn, jsiiType.interfaces ?? [], declaration, (...bases) => {
1308 if ((jsiiType.methods ?? []).length === 0) {
1309 jsiiType.datatype = true;
1310 }
1311 else if (hints.struct) {
1312 this._diagnostics.push(jsiiType.methods.reduce((diag, mthod) => {
1313 const node = bindings.getMethodRelatedNode(mthod);
1314 return node
1315 ? diag.addRelatedInformation(ts.getNameOfDeclaration(node) ?? node, 'A method is declared here')
1316 : diag;
1317 }, jsii_diagnostic_1.JsiiDiagnostic.JSII_7001_ILLEGAL_HINT.create(declaration && _findHint(declaration, 'struct'), 'struct', 'interfaces with only readonly properties').addRelatedInformationIf(ts.getNameOfDeclaration(declaration) ?? declaration, 'The annotated declartion is here')));
1318 }
1319 for (const base of bases) {
1320 if (spec.isInterfaceType(base) && !base.datatype) {
1321 jsiiType.datatype = undefined;
1322 }
1323 }
1324 const interfaceName = isInterfaceName(jsiiType.name);
1325 // If it's not a datatype the name must start with an "I".
1326 if (!jsiiType.datatype && !interfaceName) {
1327 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_8007_BEHAVIORAL_INTERFACE_NAME.create(ts.getNameOfDeclaration(declaration) ?? declaration, jsiiType.name));
1328 }
1329 // NOTE: We need to be careful with the `I` prefix for behavioral interfaces, as this can mess with PascalCase
1330 // transformations, especially with short names such as `IA`, ...
1331 const expectedName = interfaceName ? `I${Case.pascal(type.symbol.name.slice(1))}` : Case.pascal(type.symbol.name);
1332 if (expectedName !== type.symbol.name) {
1333 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_8000_PASCAL_CASED_TYPE_NAMES.create(type.symbol.declarations?.[0]?.name, type.symbol.name, expectedName));
1334 }
1335 // If the name starts with an "I" it is not intended as a datatype, so switch that off,
1336 // unless a TSDoc hint was set to force this to be considered a behavioral interface.
1337 if (jsiiType.datatype && interfaceName && !hints.struct) {
1338 delete jsiiType.datatype;
1339 }
1340 // Okay, this is a data type, check that all properties are readonly
1341 if (jsiiType.datatype) {
1342 for (const prop of jsiiType.properties ?? []) {
1343 if (!prop.immutable) {
1344 const p = type.getProperty(prop.name);
1345 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3008_STRUCT_PROPS_MUST_BE_READONLY.create(_nameOrDeclarationNode(p), p.name, jsiiType));
1346 // force property to be "readonly" since jsii languages will pass this by-value
1347 prop.immutable = true;
1348 }
1349 }
1350 }
1351 else {
1352 // This is *NOT* a data type, so it may not extend something that is one.
1353 for (const base of bases) {
1354 if (!spec.isInterfaceType(base)) {
1355 // Invalid type we already warned about earlier, just ignoring it here..
1356 continue;
1357 }
1358 if (base.datatype) {
1359 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3007_ILLEGAL_STRUCT_EXTENSION.create(type.symbol.valueDeclaration ?? type.symbol.declarations?.[0], jsiiType, base));
1360 }
1361 }
1362 }
1363 });
1364 // Check that no interface declares a member that's already declared
1365 // in a base type (not allowed in C#).
1366 const names = memberNames(jsiiType);
1367 const checkNoIntersection = (...bases) => {
1368 for (const base of bases) {
1369 if (!spec.isInterfaceType(base)) {
1370 continue;
1371 }
1372 const baseMembers = memberNames(base);
1373 for (const memberName of names) {
1374 if (baseMembers.includes(memberName)) {
1375 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_5015_REDECLARED_INTERFACE_MEMBER.create(type.symbol.valueDeclaration ?? type.symbol.declarations?.[0], memberName, jsiiType));
1376 }
1377 }
1378 // Recurse upwards
1379 this._deferUntilTypesAvailable(fqn, base.interfaces ?? [], type.symbol.valueDeclaration, checkNoIntersection);
1380 }
1381 };
1382 this._deferUntilTypesAvailable(fqn, jsiiType.interfaces ?? [], type.symbol.valueDeclaration, checkNoIntersection);
1383 return _sortMembers(jsiiType);
1384 }
1385 _visitMethod(symbol, type, ctx, declaringTypeDecl) {
1386 if (LOG.isTraceEnabled()) {
1387 LOG.trace(`Processing method: ${chalk.green(type.fqn)}#${chalk.cyan(symbol.name)}`);
1388 }
1389 const declaration = symbol.valueDeclaration;
1390 const signature = this._typeChecker.getSignatureFromDeclaration(declaration);
1391 if (!signature) {
1392 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_9004_UNABLE_TO_COMPUTE_SIGNATURE.create(declaration, symbol.name, type));
1393 return;
1394 }
1395 if (type.name === Case.pascal(symbol.name)) {
1396 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_5019_MEMBER_TYPE_NAME_CONFLICT.create(declaration.name, 'method', symbol, type).addRelatedInformationIf(declaringTypeDecl?.name ?? declaringTypeDecl, `The declaring ${type.kind} is introduced here`));
1397 }
1398 if (isProhibitedMemberName(symbol.name)) {
1399 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_5016_PROHIBITED_MEMBER_NAME.create(declaration.name, symbol.name));
1400 return;
1401 }
1402 this._warnAboutReservedWords(symbol);
1403 const parameters = signature.getParameters().map((p) => this._toParameter(p, ctx));
1404 const returnType = signature.getReturnType();
1405 const method = bindings.setMethodRelatedNode({
1406 abstract: _isAbstract(symbol, type) || undefined,
1407 name: symbol.name,
1408 parameters: parameters.length > 0 ? parameters : undefined,
1409 protected: _isProtected(symbol) || undefined,
1410 returns: _isVoid(returnType)
1411 ? undefined
1412 : this._optionalValue(returnType, declaration.type ?? declaration.name, 'return type'),
1413 async: _isPromise(returnType) || undefined,
1414 static: _isStatic(symbol) || undefined,
1415 locationInModule: this.declarationLocation(declaration),
1416 }, declaration);
1417 method.variadic = method.parameters?.some((p) => !!p.variadic) === true ? true : undefined;
1418 this._verifyConsecutiveOptionals(declaration, method.parameters);
1419 method.docs = this._visitDocumentation(symbol, ctx).docs;
1420 // If the last parameter is a datatype, verify that it does not share any field names with
1421 // other function arguments, so that it can be turned into keyword arguments by jsii frontends
1422 // that support such.
1423 const lastParamTypeRef = apply(last(parameters), (x) => x.type);
1424 const lastParamSymbol = last(signature.getParameters());
1425 if (lastParamTypeRef && spec.isNamedTypeReference(lastParamTypeRef)) {
1426 this._deferUntilTypesAvailable(symbol.name, [lastParamTypeRef], lastParamSymbol.declarations?.[0], (lastParamType) => {
1427 if (!spec.isInterfaceType(lastParamType) || !lastParamType.datatype) {
1428 return;
1429 }
1430 // Liftable datatype, make sure no parameter names match any of the properties in the datatype
1431 const propNames = this.allProperties(lastParamType);
1432 const paramNames = new Set(parameters.slice(0, parameters.length - 1).map((x) => x.name));
1433 const sharedNames = intersection(propNames, paramNames);
1434 for (const badName of sharedNames) {
1435 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_5017_POSITIONAL_KEYWORD_CONFLICT.create(declaration, badName));
1436 }
1437 });
1438 }
1439 this._validateReferencedDocParams(method, symbol);
1440 type.methods = type.methods ?? [];
1441 if (type.methods.find((m) => m.name === method.name && m.static === method.static) != null) {
1442 LOG.trace(`Dropping re-declaration of ${chalk.green(type.fqn)}#${chalk.cyan(method.name)}`);
1443 return;
1444 }
1445 type.methods.push(method);
1446 }
1447 _warnAboutReservedWords(symbol) {
1448 if (!warnings_1.enabledWarnings['reserved-word']) {
1449 return;
1450 }
1451 const reservingLanguages = (0, reserved_words_1.isReservedName)(symbol.name);
1452 if (reservingLanguages) {
1453 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_5018_RESERVED_WORD.create(_nameOrDeclarationNode(symbol), symbol.name, reservingLanguages));
1454 }
1455 }
1456 _visitProperty(symbol, type, ctx, declaringTypeDecl) {
1457 if (type.properties?.find((p) => p.name === symbol.name)) {
1458 /*
1459 * Second declaration of the same property. For example, if code specifies a getter & setter signature,
1460 * there will be one pass for each of the signatures, but we can process only the first encountered. The
1461 * typescript compiler will take care of making sure we don't have conflicting declarations, anyway.
1462 */
1463 return;
1464 }
1465 if (LOG.isTraceEnabled()) {
1466 LOG.trace(`Processing property: ${chalk.green(type.fqn)}#${chalk.cyan(symbol.name)}`);
1467 }
1468 const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
1469 const signature = declaration;
1470 if (type.name === Case.pascal(symbol.name)) {
1471 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_5019_MEMBER_TYPE_NAME_CONFLICT.create(signature.name, 'property', symbol, type).addRelatedInformationIf(declaringTypeDecl?.name ?? declaringTypeDecl, `The declaring ${type.kind} is introduced here`));
1472 }
1473 if (isProhibitedMemberName(symbol.name)) {
1474 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_5016_PROHIBITED_MEMBER_NAME.create(symbol.valueDeclaration ?? symbol.declarations?.[0], symbol.name));
1475 return;
1476 }
1477 this._warnAboutReservedWords(symbol);
1478 const property = bindings.setPropertyRelatedNode({
1479 ...this._optionalValue(this._typeChecker.getTypeOfSymbolAtLocation(symbol, signature), signature.type ?? signature.name, 'property type'),
1480 abstract: _isAbstract(symbol, type) || undefined,
1481 name: symbol.name,
1482 protected: _isProtected(symbol) || undefined,
1483 static: _isStatic(symbol) || undefined,
1484 locationInModule: this.declarationLocation(signature),
1485 }, signature);
1486 if (ts.isGetAccessor(signature)) {
1487 property.immutable = true;
1488 for (const decl of symbol.getDeclarations() ?? []) {
1489 if (!ts.isSetAccessor(decl)) {
1490 continue;
1491 }
1492 delete property.immutable;
1493 // Verify the setter doesn't have a Separate Write Type (SWT)
1494 const valueParam = decl.parameters[0];
1495 if (valueParam?.type == null) {
1496 // If there is no type node, there can't be a SWT
1497 continue;
1498 }
1499 const paramType = this._typeChecker.getTypeFromTypeNode(valueParam.type);
1500 const paramOptionalValue = this._optionalValue(paramType, valueParam.type, 'parameter type');
1501 if (property.optional !== paramOptionalValue.optional ||
1502 (0, spec_1.describeTypeReference)(property.type) !== (0, spec_1.describeTypeReference)(paramOptionalValue.type)) {
1503 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_1005_SEPARATE_WRITE_TYPE.create(valueParam.type).addRelatedInformation(signature.type ?? signature.name, 'The getter signature is declared here'));
1504 }
1505 }
1506 }
1507 else {
1508 property.immutable = (ts.getCombinedModifierFlags(signature) & ts.ModifierFlags.Readonly) !== 0 || undefined;
1509 }
1510 if (signature.questionToken) {
1511 property.optional = true;
1512 }
1513 if (property.static && property.immutable && ts.isPropertyDeclaration(signature) && signature.initializer) {
1514 property.const = true;
1515 }
1516 property.docs = this._visitDocumentation(symbol, ctx).docs;
1517 type.properties = type.properties ?? [];
1518 if (type.properties.find((prop) => prop.name === property.name && prop.static === property.static) != null) {
1519 LOG.trace(`Dropping re-declaration of ${chalk.green(type.fqn)}#${chalk.cyan(property.name)}`);
1520 return;
1521 }
1522 type.properties.push(property);
1523 }
1524 _toParameter(paramSymbol, ctx) {
1525 if (LOG.isTraceEnabled()) {
1526 LOG.trace(`Processing parameter: ${chalk.cyan(paramSymbol.name)}`);
1527 }
1528 const paramDeclaration = paramSymbol.valueDeclaration;
1529 this._warnAboutReservedWords(paramSymbol);
1530 const parameter = bindings.setParameterRelatedNode({
1531 ...this._optionalValue(this._typeChecker.getTypeAtLocation(paramDeclaration), paramDeclaration.type ?? paramDeclaration.name, 'parameter type'),
1532 name: paramSymbol.name,
1533 variadic: paramDeclaration.dotDotDotToken && true,
1534 }, paramDeclaration);
1535 if (parameter.variadic && spec.isCollectionTypeReference(parameter.type)) {
1536 // TypeScript types variadic parameters as an array, but JSII uses the item-type instead.
1537 parameter.type = parameter.type.collection.elementtype;
1538 }
1539 else if (paramDeclaration.initializer || paramDeclaration.questionToken) {
1540 // Optional parameters have an inherently null-able type.
1541 parameter.optional = true;
1542 }
1543 parameter.docs = this._visitDocumentation(paramSymbol, ctx.removeStability()).docs;
1544 // Don't rewrite doc comment here on purpose -- instead, we add them as '@param'
1545 // into the parent's doc comment.
1546 return parameter;
1547 }
1548 _typeReference(type, declaration, purpose) {
1549 const optionalValue = this._optionalValue(type, declaration, purpose);
1550 if (optionalValue.optional) {
1551 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3999_INCOHERENT_TYPE_MODEL.create(declaration, 'Encountered optional value in location where a plain type reference is expected'));
1552 }
1553 return optionalValue.type;
1554 }
1555 _optionalValue(type, declaration, purpose) {
1556 const isThisType = _isThisType(type, this._typeChecker, declaration?.parent);
1557 if (type.isLiteral() && _isEnumLike(type)) {
1558 type = this._typeChecker.getBaseTypeOfLiteralType(type);
1559 }
1560 else {
1561 type = this._typeChecker.getApparentType(type);
1562 }
1563 const primitiveType = _tryMakePrimitiveType.call(this);
1564 if (primitiveType) {
1565 return { type: primitiveType };
1566 }
1567 if (type.isUnion() && !_isEnumLike(type)) {
1568 return _unionType.call(this);
1569 }
1570 if (!type.symbol) {
1571 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_1001_TYPE_HAS_NO_SYMBOL.create(declaration));
1572 return { type: spec.CANONICAL_ANY };
1573 }
1574 if (type.symbol.name === 'Array') {
1575 return { type: _arrayType.call(this) };
1576 }
1577 if (type.symbol.name === '__type' && type.symbol.members) {
1578 return { type: _mapType.call(this) };
1579 }
1580 if (type.symbol.escapedName === 'Promise') {
1581 const typeRef = type;
1582 if (!typeRef.typeArguments || typeRef.typeArguments.length !== 1) {
1583 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_1002_UNSPECIFIED_PROMISE.create(declaration));
1584 return { type: spec.CANONICAL_ANY };
1585 }
1586 return {
1587 type: this._typeReference(typeRef.typeArguments[0], declaration, purpose),
1588 };
1589 }
1590 const fqn = this._getFQN(type, declaration, purpose, isThisType);
1591 if (fqn == null) {
1592 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_9997_UNKNOWN_ERROR.create(declaration, new Error('Could not determine FQN')));
1593 return { type: { fqn: '' } };
1594 }
1595 return {
1596 type: { fqn },
1597 };
1598 function _arrayType() {
1599 const typeRef = type;
1600 let elementtype;
1601 if (typeRef.typeArguments?.length === 1) {
1602 elementtype = this._typeReference(typeRef.typeArguments[0], declaration, 'list element type');
1603 }
1604 else {
1605 const count = typeRef.typeArguments ? typeRef.typeArguments.length : 'none';
1606 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_1003_UNSUPPORTED_TYPE.create(declaration, `Array references must have exactly one type argument (found ${count})`));
1607 elementtype = spec.CANONICAL_ANY;
1608 }
1609 return {
1610 collection: {
1611 elementtype,
1612 kind: spec.CollectionKind.Array,
1613 },
1614 };
1615 }
1616 function _mapType() {
1617 let elementtype;
1618 const objectType = type.getStringIndexType();
1619 if (objectType) {
1620 elementtype = this._typeReference(objectType, declaration, 'map element type');
1621 }
1622 else {
1623 const typeDecl = type.symbol.declarations?.[0];
1624 if (typeDecl != null &&
1625 ts.isTypeLiteralNode(typeDecl) &&
1626 typeDecl.members.length == 1 &&
1627 ts.isIndexSignatureDeclaration(typeDecl.members[0]) &&
1628 typeDecl.members[0].parameters[0].type != null &&
1629 ts.isTemplateLiteralTypeNode(typeDecl.members[0].parameters[0].type)) {
1630 const indexTypeNode = typeDecl.members[0].type;
1631 const indexType = this._typeChecker.getTypeFromTypeNode(indexTypeNode);
1632 elementtype = this._typeReference(indexType, indexTypeNode, 'map element type');
1633 }
1634 else {
1635 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_1003_UNSUPPORTED_TYPE.create(declaration, 'Only string-indexed map types are supported'));
1636 elementtype = spec.CANONICAL_ANY;
1637 }
1638 }
1639 return {
1640 collection: {
1641 elementtype,
1642 kind: spec.CollectionKind.Map,
1643 },
1644 };
1645 }
1646 function _tryMakePrimitiveType() {
1647 if (!type.symbol) {
1648 if (type.flags & ts.TypeFlags.Object) {
1649 if (isTupleType(type)) {
1650 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_1999_UNSUPPORTED.create(declaration, { what: 'Tuple types', alternative: 'arrays' }));
1651 }
1652 return { primitive: spec.PrimitiveType.Json };
1653 }
1654 if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
1655 return spec.CANONICAL_ANY;
1656 }
1657 }
1658 else if (type.symbol.valueDeclaration &&
1659 isUnder(type.symbol.valueDeclaration.getSourceFile().fileName, this.stdlib)) {
1660 switch (type.symbol.name) {
1661 case 'Boolean':
1662 return { primitive: spec.PrimitiveType.Boolean };
1663 case 'Date':
1664 return { primitive: spec.PrimitiveType.Date };
1665 case 'Number':
1666 return { primitive: spec.PrimitiveType.Number };
1667 case 'String':
1668 return { primitive: spec.PrimitiveType.String };
1669 }
1670 }
1671 // Not a primitive type!
1672 return undefined;
1673 }
1674 function _unionType() {
1675 const types = new Array();
1676 let optional;
1677 for (const subType of type.types) {
1678 if (subType.flags & ts.TypeFlags.Undefined) {
1679 optional = true;
1680 continue;
1681 }
1682 // eslint-disable-next-line no-await-in-loop
1683 const resolvedType = this._typeReference(subType, declaration, purpose);
1684 if (types.some((ref) => deepEqual(ref, resolvedType))) {
1685 continue;
1686 }
1687 types.push(resolvedType);
1688 }
1689 return types.length === 1 ? { optional, type: types[0] } : { optional, type: { union: { types } } };
1690 }
1691 }
1692 callDeferredsInOrder() {
1693 // Do a topological call order of all deferreds.
1694 while (this._deferred.length > 0) {
1695 // All fqns in dependency lists that don't have any pending
1696 // deferreds themselves can be executed now, so are removed from
1697 // dependency lists.
1698 const pendingFqns = new Set(this._deferred.map((x) => x.fqn));
1699 for (const deferred of this._deferred) {
1700 restrictDependenciesTo(deferred, pendingFqns);
1701 }
1702 // Invoke all deferreds with no more dependencies and remove them from the list.
1703 let invoked = false;
1704 for (let i = 0; i < this._deferred.length; i++) {
1705 if (this._deferred[i].dependedFqns.length === 0) {
1706 const deferred = this._deferred.splice(i, 1)[0];
1707 deferred.cb();
1708 invoked = true;
1709 }
1710 }
1711 if (!invoked) {
1712 // Apparently we're stuck. Complain loudly.
1713 throw new Error(`Could not invoke any more deferreds, cyclic dependency? Remaining: ${JSON.stringify(this._deferred, undefined, 2)}`);
1714 }
1715 }
1716 /**
1717 * Retain only elements in the dependencyfqn that are also in the set
1718 */
1719 function restrictDependenciesTo(def, fqns) {
1720 def.dependedFqns = def.dependedFqns.filter(fqns.has.bind(fqns));
1721 }
1722 }
1723 /**
1724 * Return the set of all (inherited) properties of an interface
1725 */
1726 allProperties(root) {
1727 const ret = new Set();
1728 recurse.call(this, root);
1729 return ret;
1730 function recurse(int) {
1731 for (const property of int.properties ?? []) {
1732 ret.add(property.name);
1733 }
1734 for (const baseRef of int.interfaces ?? []) {
1735 const base = this._dereference(baseRef, undefined);
1736 if (!base) {
1737 throw new Error('Impossible to have unresolvable base in allProperties()');
1738 }
1739 if (!spec.isInterfaceType(base)) {
1740 throw new Error('Impossible to have non-interface base in allProperties()');
1741 }
1742 recurse.call(this, base);
1743 }
1744 }
1745 }
1746 _verifyConsecutiveOptionals(node, parameters) {
1747 if (!parameters) {
1748 return;
1749 }
1750 const remaining = [...parameters].reverse();
1751 while (remaining.length > 0) {
1752 const current = remaining.pop();
1753 if (current.optional) {
1754 const offender = remaining.find((p) => !p.optional && !p.variadic);
1755 if (offender == null) {
1756 continue;
1757 }
1758 this._diagnostics.push(jsii_diagnostic_1.JsiiDiagnostic.JSII_3009_OPTIONAL_PARAMETER_BEFORE_REQUIRED.create(node, current, offender));
1759 delete current.optional;
1760 }
1761 }
1762 }
1763 /**
1764 * Updates the runtime type info with the fully-qualified name for the current class definition.
1765 * Used by the runtime type info injector to add this information to the compiled file.
1766 */
1767 registerExportedClassFqn(clazz, fqn) {
1768 this.runtimeTypeInfoInjector.registerClassFqn(clazz, fqn);
1769 }
1770 /**
1771 * Return only those submodules from the submodules list that are submodules inside this
1772 * assembly.
1773 */
1774 mySubmodules() {
1775 return Array.from(this._submodules.values()).filter((m) => m.fqn.startsWith(`${this.projectInfo.name}.`));
1776 }
1777 findPackageInfo(fromDir) {
1778 if (this._packageInfoCache.has(fromDir)) {
1779 return this._packageInfoCache.get(fromDir);
1780 }
1781 const packageInfo = _findPackageInfo.call(this, fromDir);
1782 this._packageInfoCache.set(fromDir, packageInfo);
1783 return packageInfo;
1784 function _findPackageInfo(dir) {
1785 const filePath = path.join(dir, 'package.json');
1786 if (fs.existsSync(filePath)) {
1787 return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
1788 }
1789 const parent = path.dirname(dir);
1790 if (parent === dir) {
1791 return undefined;
1792 }
1793 return this.findPackageInfo(parent);
1794 }
1795 }
1796}
1797exports.Assembler = Assembler;
1798function _fingerprint(assembly) {
1799 delete assembly.fingerprint;
1800 assembly = sortJson(assembly);
1801 const fingerprint = crypto.createHash('sha256').update(JSON.stringify(assembly)).digest('base64');
1802 return { ...assembly, fingerprint };
1803}
1804function _isAbstract(symbol, declaringType) {
1805 // everything is abstract in interfaces
1806 if (declaringType.kind === spec.TypeKind.Interface) {
1807 return true;
1808 }
1809 return (!!symbol.valueDeclaration &&
1810 (ts.getCombinedModifierFlags(symbol.valueDeclaration) & ts.ModifierFlags.Abstract) !== 0);
1811}
1812function _isEnumLike(type) {
1813 return (type.flags & ts.TypeFlags.EnumLike) !== 0;
1814}
1815function _isExported(node) {
1816 return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0;
1817}
1818/**
1819 * Members with names starting with `_` (and marked as @internal) and members
1820 * that are private are hidden.
1821 *
1822 * @param symbol the symbol which should be assessed
1823 *
1824 * @return `true` if the symbol should be hidden
1825 */
1826function _isPrivate(symbol) {
1827 // Private identifiers are always private...
1828 if (symbol.name.startsWith('#')) {
1829 return true;
1830 }
1831 const TYPE_DECLARATION_KINDS = new Set([
1832 ts.SyntaxKind.ClassDeclaration,
1833 ts.SyntaxKind.InterfaceDeclaration,
1834 ts.SyntaxKind.EnumDeclaration,
1835 ]);
1836 // if the symbol doesn't have a value declaration, we are assuming it's a type (enum/interface/class)
1837 // and check that it has an "export" modifier
1838 if (!isInternalSymbol(symbol) &&
1839 (!symbol.valueDeclaration || TYPE_DECLARATION_KINDS.has(symbol.valueDeclaration.kind))) {
1840 let hasExport = false;
1841 for (const decl of symbol.declarations ?? []) {
1842 if (ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Export) {
1843 hasExport = true;
1844 break;
1845 }
1846 // Handle nested classes from project references
1847 if (ts.isModuleBlock(decl.parent)) {
1848 const moduleDeclaration = decl.parent.parent;
1849 const modifiers = ts.getCombinedModifierFlags(moduleDeclaration);
1850 // The trick is the module is declared as ambient & exported
1851 if ((modifiers & ts.ModifierFlags.Ambient) !== 0 && (modifiers & ts.ModifierFlags.Export) !== 0) {
1852 hasExport = true;
1853 break;
1854 }
1855 }
1856 }
1857 return !hasExport;
1858 }
1859 const decl = symbol.valueDeclaration ?? symbol.declarations?.[0];
1860 return decl != null && (ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Private) !== 0;
1861}
1862function _hasInternalJsDocTag(symbol) {
1863 return symbol.getJsDocTags().some((t) => t.name === 'internal');
1864}
1865function _isProtected(symbol) {
1866 return (!!symbol.valueDeclaration &&
1867 (ts.getCombinedModifierFlags(symbol.valueDeclaration) & ts.ModifierFlags.Protected) !== 0);
1868}
1869function _isStatic(symbol) {
1870 return (!!symbol.valueDeclaration && (ts.getCombinedModifierFlags(symbol.valueDeclaration) & ts.ModifierFlags.Static) !== 0);
1871}
1872/**
1873 * Determines whether a given type is void or Promise<void>.
1874 *
1875 * @param type the tested type
1876 *
1877 * @returns `true` if the type is void or Promise<void>
1878 */
1879function _isVoid(type) {
1880 if (_isPromise(type)) {
1881 const typeRef = type;
1882 return typeRef.typeArguments != null && typeRef.typeArguments.length === 1 && _isVoid(typeRef.typeArguments[0]);
1883 }
1884 return (type.flags & ts.TypeFlags.Void) !== 0;
1885}
1886function _isPromise(type) {
1887 return type.symbol?.escapedName === 'Promise';
1888}
1889function _sortMembers(type) {
1890 type.methods = type.methods && _sort(type.methods);
1891 type.properties = type.properties && _sort(type.properties);
1892 return type;
1893 /**
1894 * Sorts a member array such that:
1895 * 1. Static members appear first
1896 * 2. Immutable members appear first
1897 * 3. Non-optional members appear first
1898 * 4. Members appear in lexicographical order
1899 *
1900 * @param values the array of members to be sorted
1901 *
1902 * @return a sorted copy of ``values``
1903 */
1904 function _sort(values) {
1905 if (!values) {
1906 return values;
1907 }
1908 return values.sort(_comparator);
1909 function _comparator(lval, rval) {
1910 return _format(lval).localeCompare(_format(rval));
1911 function _format(val) {
1912 return [val.static ? '0' : '1', val.immutable ? '0' : '1', !val.optional ? '0' : '1', val.name].join('|');
1913 }
1914 }
1915 }
1916}
1917/**
1918 * Return the last element from a list
1919 */
1920function last(xs) {
1921 return xs.length > 0 ? xs[xs.length - 1] : undefined;
1922}
1923/**
1924 * Apply a function to a value if it's not equal to undefined
1925 */
1926function apply(x, fn) {
1927 return x !== undefined ? fn(x) : undefined;
1928}
1929/**
1930 * Return the intersection of two sets
1931 */
1932function intersection(xs, ys) {
1933 const ret = new Set();
1934 for (const x of xs) {
1935 if (ys.has(x)) {
1936 ret.add(x);
1937 }
1938 }
1939 return ret;
1940}
1941/**
1942 * Return all members names of a JSII interface type
1943 *
1944 * Returns empty string for a non-interface type.
1945 */
1946function memberNames(jsiiType) {
1947 return Object.keys(typeMembers(jsiiType)).filter((n) => n !== '');
1948}
1949function typeMembers(jsiiType) {
1950 const ret = {};
1951 for (const prop of jsiiType.properties ?? []) {
1952 ret[prop.name] = prop;
1953 }
1954 for (const method of jsiiType.methods ?? []) {
1955 ret[method.name ?? ''] = method;
1956 }
1957 return ret;
1958}
1959/**
1960 * Whether or not the given name is conventionally an interface name
1961 *
1962 * It's an interface name if it starts with I and has another capital
1963 * (so we don't mark IonicColumnProps as an interface).
1964 */
1965function isInterfaceName(name) {
1966 return name.length >= 2 && name.startsWith('I') && name.charAt(1).toUpperCase() === name.charAt(1);
1967}
1968function getConstructor(type) {
1969 return type.symbol.members?.get(ts.InternalSymbolName.Constructor);
1970}
1971function* intersect(xs, ys) {
1972 for (const x of xs) {
1973 if (ys.has(x)) {
1974 yield x;
1975 }
1976 }
1977}
1978function noEmptyDict(xs) {
1979 if (xs == null || Object.keys(xs).length === 0) {
1980 return undefined;
1981 }
1982 return xs;
1983}
1984function toDependencyClosure(assemblies) {
1985 const result = {};
1986 for (const assembly of assemblies) {
1987 if (!assembly.targets) {
1988 continue;
1989 }
1990 result[assembly.name] = {
1991 submodules: cleanUp(assembly.submodules),
1992 targets: assembly.targets,
1993 };
1994 }
1995 return result;
1996 /**
1997 * Removes unneeded fields from the entries part of the `dependencyClosure`
1998 * property. Fields such as `readme` are not necessary and can bloat up the
1999 * assembly object.
2000 *
2001 * This removes the `readme` and `locationInModule` fields from the submodule
2002 * descriptios if present.
2003 *
2004 * @param submodules the submodules list to clean up.
2005 *
2006 * @returns the cleaned up submodules list.
2007 */
2008 function cleanUp(submodules) {
2009 if (submodules == null) {
2010 return submodules;
2011 }
2012 const clean = {};
2013 for (const [fqn, { targets }] of Object.entries(submodules)) {
2014 clean[fqn] = { targets };
2015 }
2016 return clean;
2017 }
2018}
2019function toSubmoduleDeclarations(submodules) {
2020 const result = {};
2021 for (const submodule of submodules) {
2022 result[submodule.fqn] = {
2023 locationInModule: submodule.locationInModule,
2024 targets: submodule.targets,
2025 readme: submodule.readme,
2026 symbolId: submodule.symbolId,
2027 };
2028 }
2029 return result;
2030}
2031/**
2032 * Check whether this type is the intrinsic TypeScript "error type"
2033 *
2034 * This type is returned if type lookup fails. Unfortunately no public
2035 * accessors for it are exposed.
2036 */
2037function isErrorType(t) {
2038 return t.intrinsicName === 'error';
2039}
2040/**
2041 * Those have specific semantics in certain languages that don't always translate cleanly in others
2042 * (like how equals/hashCode are not a thing in Javascript, but carry meaning in Java and C#). The
2043 * `build` name is reserved for generated code (Java builders use that).
2044 */
2045const PROHIBITED_MEMBER_NAMES = ['build', 'equals', 'hashcode'];
2046/**
2047 * Whether the given name is prohibited
2048 */
2049function isProhibitedMemberName(name) {
2050 return PROHIBITED_MEMBER_NAMES.includes(name.toLowerCase());
2051}
2052/**
2053 * Information about the context in which a declaration is emitted.
2054 */
2055class EmitContext {
2056 constructor(namespace, stability) {
2057 this.namespace = namespace;
2058 this.stability = stability;
2059 }
2060 /**
2061 * Create a new EmitContext by appending a namespace entry at the end.
2062 * @param element the new namespace entry.
2063 */
2064 appendNamespace(element) {
2065 return new EmitContext([...this.namespace, element], this.stability);
2066 }
2067 /**
2068 * Create a new EmitContext by replacing the stability.
2069 * @param stability the new stability, if available.
2070 */
2071 replaceStability(stability) {
2072 if (!stability) {
2073 return this;
2074 }
2075 return new EmitContext(this.namespace, stability);
2076 }
2077 /**
2078 * Create a new EmitContext without stability.
2079 */
2080 removeStability() {
2081 return new EmitContext(this.namespace, undefined);
2082 }
2083}
2084function inferRootDir(program) {
2085 const directories = program
2086 .getRootFileNames()
2087 .filter((fileName) => {
2088 const sourceFile = program.getSourceFile(fileName);
2089 return (sourceFile != null &&
2090 !program.isSourceFileFromExternalLibrary(sourceFile) &&
2091 !program.isSourceFileDefaultLibrary(sourceFile));
2092 })
2093 .map((fileName) => path.relative(program.getCurrentDirectory(), path.dirname(fileName)))
2094 .map(segmentPath);
2095 const maxPrefix = Math.min(...directories.map((segments) => segments.length - 1));
2096 let commonIndex = -1;
2097 while (commonIndex < maxPrefix && new Set(directories.map((segments) => segments[commonIndex + 1])).size === 1) {
2098 commonIndex++;
2099 }
2100 if (commonIndex < 0) {
2101 return undefined;
2102 }
2103 return directories[0][commonIndex];
2104 function segmentPath(fileName) {
2105 const result = new Array();
2106 for (let parent = fileName; parent !== path.dirname(parent); parent = path.dirname(parent)) {
2107 result.unshift(parent);
2108 }
2109 return result;
2110 }
2111}
2112/**
2113 * Determines whether the provided type is a single-valued enum. It is necessary
2114 * to check as enums are union-like in the type model, and single-valued enum
2115 * types are actually reduced to the only available literal, which can trip
2116 * the assembler.
2117 *
2118 * @param type the type being checked.
2119 * @param typeChecker the type checker to use to get more information.
2120 *
2121 * @return `true` if `type` is a single-valued enum type.
2122 */
2123function isSingleValuedEnum(type, typeChecker) {
2124 if (type.isLiteral() && _isEnumLike(type)) {
2125 // Single-Valued enums are reduced to the only literal available.
2126 return type === typeChecker.getBaseTypeOfLiteralType(type);
2127 }
2128 return false;
2129}
2130/**
2131 * Checks is the provided type is "this" (as a type annotation).
2132 *
2133 * @param type the validated type.
2134 * @param typeChecker the type checker.
2135 *
2136 * @returns `true` iif the type is `this`
2137 */
2138function _isThisType(type, typeChecker, enclosingDeclaration) {
2139 return (typeChecker.typeToTypeNode(type, enclosingDeclaration, ts.NodeBuilderFlags.None)?.kind === ts.SyntaxKind.ThisKeyword);
2140}
2141/**
2142 * Gets the name node for a given symbol; or it's first declaration if no name can be found. This is
2143 * intended for use in placing problem markers on the right location.
2144 *
2145 * @param symbol the symbol for which the name node is needed.
2146 *
2147 * @returns the name node for the symbol, or the symbol's first declaration.
2148 */
2149function _nameOrDeclarationNode(symbol) {
2150 const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
2151 if (declaration == null) {
2152 return undefined;
2153 }
2154 return ts.getNameOfDeclaration(declaration) ?? declaration;
2155}
2156function _findHint(decl, hint) {
2157 const [node] = ts.getAllJSDocTags(decl, (tag) => tag.tagName.text === hint);
2158 return node;
2159}
2160/**
2161 * Resolve a Type to Symbol, taking into account single-valued enums which have a bug
2162 *
2163 * Bug reference: https://github.com/microsoft/TypeScript/issues/46755
2164 */
2165function symbolFromType(type, typeChecker) {
2166 if ((type.flags & ts.TypeFlags.EnumLiteral) === 0) {
2167 return type.symbol;
2168 }
2169 const decl = type.symbol.declarations?.[0];
2170 if (!decl) {
2171 return type.symbol;
2172 }
2173 if (!ts.isEnumMember(decl)) {
2174 return type.symbol;
2175 }
2176 const parentDecl = decl.parent;
2177 if (!parentDecl || !ts.isEnumDeclaration(parentDecl)) {
2178 return type.symbol;
2179 }
2180 const name = ts.getNameOfDeclaration(parentDecl);
2181 if (!name) {
2182 return type.symbol;
2183 }
2184 return typeChecker.getSymbolAtLocation(name) ?? type.symbol;
2185}
2186const SYMBOLID_CACHE = new WeakMap();
2187/**
2188 * Build and return an index of { symbolId -> fqn }
2189 *
2190 * Uses a cache for performance reasons.
2191 */
2192function symbolIdIndex(asm) {
2193 const existing = SYMBOLID_CACHE.get(asm);
2194 if (existing) {
2195 return existing;
2196 }
2197 const ret = buildIndex();
2198 SYMBOLID_CACHE.set(asm, ret);
2199 return ret;
2200 function buildIndex() {
2201 const index = {};
2202 for (const [fqn, type] of Object.entries(asm.types ?? {})) {
2203 if (type.symbolId) {
2204 index[type.symbolId] = fqn;
2205 }
2206 }
2207 return index;
2208 }
2209}
2210function getSymbolFromDeclaration(decl, typeChecker) {
2211 const name = ts.getNameOfDeclaration(decl);
2212 return name ? typeChecker.getSymbolAtLocation(name) : undefined;
2213}
2214function isTupleType(type) {
2215 if (type.objectFlags & ts.ObjectFlags.Tuple) {
2216 return true;
2217 }
2218 if (type.objectFlags & ts.ObjectFlags.Reference) {
2219 return isTupleType(type.target);
2220 }
2221 return false;
2222}
2223function isUnder(file, dir) {
2224 const relative = path.relative(dir, file);
2225 return !relative.startsWith(path.sep) && !relative.startsWith('..');
2226}
2227function loadAndRenderReadme(readmePath, projectRoot) {
2228 if (!fs.existsSync(readmePath)) {
2229 return undefined;
2230 }
2231 return {
2232 markdown: literate
2233 .includeAndRenderExamples(literate.loadFromFile(readmePath), literate.fileSystemLoader(path.dirname(readmePath)), projectRoot)
2234 .join('\n'),
2235 };
2236}
2237const INTERNAL_SYMBOLS = new Set(Object.values(ts.InternalSymbolName));
2238function isInternalSymbol(symbol) {
2239 return INTERNAL_SYMBOLS.has(symbol.name);
2240}
2241//# sourceMappingURL=assembler.js.map
\No newline at end of file