carrick 0.3.118 → 0.3.120

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,7 @@
14
14
  * | `compare` | `=== !== == != < > <= >= instanceof in` | the left operand | the left operand |
15
15
  * | `ident` | an identifier in expression position | itself | itself |
16
16
  * | `impl` | a `call` or `member` whose declaration has no body | an implementation of it | nothing |
17
+ * | `decl` | a `call` or `member` on a union of declaring types | one of those declarations | nothing |
17
18
  * | `jsx` | a JSX opening or self-closing element's tag | the tag's name | the tag |
18
19
  *
19
20
  * An `impl` row (version 2, carrick#2430) follows the `call` or `member` row
@@ -24,9 +25,18 @@
24
25
  * initializer. Its `decl_*` name the implementing declaration. Past
25
26
  * {@link IMPL_CAP} the row is one, with {@link FLAG_TOO_MANY} and nothing else.
26
27
  *
27
- * A row's key is `(start, end, kind)`, except that several `impl` rows share
28
- * one: TypeScript `getStart()`/`getEnd()` of the node, in UTF-16 code units
29
- * from the start of the file, which is what the scanner's `sidecar_position`
28
+ * A `decl` row (version 3, carrick#2448) follows the `call` or `member` row
29
+ * it belongs to, before its `impl` rows, and has its span: one per
30
+ * declaration of the member when the receiver is a union whose constituents
31
+ * declare it at more than one place (`(pick ? a : b).get()` with two
32
+ * classes). That row names only the first of them. Each constituent's
33
+ * declaration is its own member's, or, for a member with none (a mapped
34
+ * type's), the declaration of the constituent type itself. Sorted and capped
35
+ * as `impl` rows are; `type_text` is `null`.
36
+ *
37
+ * A row's key is `(start, end, kind)`, except that several `impl` rows, and
38
+ * several `decl` rows, share one: TypeScript `getStart()`/`getEnd()` of the
39
+ * node, in UTF-16 code units from the start of the file, which is what the scanner's `sidecar_position`
30
40
  * gives for an SWC span. Every path written is relative to the scanned repo's
31
41
  * root, with `/` separators.
32
42
  *
@@ -49,7 +59,7 @@ import { WriteGuard, installedPackageDirectory, isDeclarationFileName, moduleExp
49
59
  import { attributedDeclaration, installedPackageOf } from './declaration-attribution.js';
50
60
  import { typeToStringAsDeclared } from './print-type.js';
51
61
  /** The format's version, written in the header. */
52
- export const POSITION_ROWS_VERSION = 2;
62
+ export const POSITION_ROWS_VERSION = 3;
53
63
  /** The type is `any`, declared or inferred. Never set with the error type. */
54
64
  export const FLAG_ANY = 1;
55
65
  /** The type is the compiler's error type: something did not resolve. */
@@ -58,9 +68,18 @@ export const FLAG_ERROR_TYPE = 2;
58
68
  export const FLAG_CHECKER_THREW = 4;
59
69
  /** The declaration is outside the checkout, not the default library and in no installed package. */
60
70
  export const FLAG_DECLARATION_UNNAMED = 8;
61
- /** An `impl` row standing for more implementations than {@link IMPL_CAP}; it names none. */
71
+ /** An `impl` or `decl` row standing for more than {@link IMPL_CAP} rows of its kind; it names none. */
62
72
  export const FLAG_TOO_MANY = 16;
63
- /** The most implementations one declaration's `impl` rows list. */
73
+ /**
74
+ * The declaration is published under the row's symbol name: its module
75
+ * exports it by that name, or it is a global (a script's top-level
76
+ * declaration, or one in `declare global`). A member of a class, interface,
77
+ * type literal or object literal never is (carrick#2508).
78
+ */
79
+ export const FLAG_PUBLISHED = 32;
80
+ /** The declaration is what its module exports as `export =` or `default` (carrick#2508). */
81
+ export const FLAG_MODULE_DEFAULT = 64;
82
+ /** The most `impl` rows one declaration lists, and the most `decl` rows one position lists. */
64
83
  export const IMPL_CAP = 16;
65
84
  const COMPARE_OPERATORS = new Set([
66
85
  ts.SyntaxKind.EqualsEqualsEqualsToken,
@@ -379,6 +398,8 @@ class PositionWalker {
379
398
  implementations = new Map();
380
399
  /** The same for a member whose symbol has no declaration, keyed by symbol. */
381
400
  undeclaredImplementations = new Map();
401
+ /** Each module's exports by name, and its `export =` and `default` targets, aliases followed. */
402
+ moduleExports = new Map();
382
403
  constructor(program, service, paths, loader) {
383
404
  this.program = program;
384
405
  this.service = service;
@@ -394,6 +415,8 @@ class PositionWalker {
394
415
  const row = this.row(kind, node, sourceFile);
395
416
  rows.push(row);
396
417
  if ((kind === 'call' || kind === 'member') && !(row[8] & FLAG_CHECKER_THREW)) {
418
+ for (const decl of this.declRows(kind, node))
419
+ rows.push(['decl', row[1], row[2], ...tail(decl)]);
397
420
  for (const impl of this.implRows(kind, node))
398
421
  rows.push(['impl', row[1], row[2], ...tail(impl)]);
399
422
  }
@@ -424,6 +447,9 @@ class PositionWalker {
424
447
  this.ownerDeclaration(kind, node, symbol);
425
448
  if (!declaration)
426
449
  return [kind, start, end, ts.symbolName(symbol), null, null, null, typeText, flags];
450
+ // A declaration named through its owner's type is a member.
451
+ if ((symbol.declarations ?? []).includes(declaration))
452
+ flags |= this.publication(symbol, declaration);
427
453
  // A file's own module symbol is named by the compiler as the quoted
428
454
  // absolute file name (`"/<checkout>/pkg/index"`). Name it as `decl_file`
429
455
  // names the file. A symbol that merely lives in a file (CommonJS
@@ -566,20 +592,135 @@ class PositionWalker {
566
592
  * constituent that has the property; with several the row is left as it is.
567
593
  */
568
594
  ownerDeclaration(kind, node, symbol) {
569
- if (kind !== 'member' && kind !== 'call')
570
- return undefined;
571
- const named = nameNodeOf(kind, node);
572
- const access = named?.parent;
573
- if (!access || !(ts.isPropertyAccessExpression(access) || ts.isElementAccessExpression(access))) {
595
+ const receiver = this.receiverOf(kind, node);
596
+ if (!receiver)
574
597
  return undefined;
575
- }
576
- const receiver = this.checker.getTypeAtLocation(access.expression);
577
598
  const holders = (receiver.isUnionOrIntersection() ? receiver.types : [receiver]).filter((constituent) => constituent.getProperty(symbol.name) !== undefined);
578
599
  if (holders.length !== 1)
579
600
  return undefined;
580
- const owner = holders[0].getSymbol() ?? holders[0].aliasSymbol;
601
+ return this.typeDeclaration(holders[0]);
602
+ }
603
+ /** The declaration of a type's symbol, else of its alias symbol: a mapped type node, or the alias naming it. */
604
+ typeDeclaration(type) {
605
+ const owner = type.getSymbol() ?? type.aliasSymbol;
581
606
  return attributedDeclaration(() => this.program, owner?.declarations ?? []);
582
607
  }
608
+ /**
609
+ * How `declaration`'s module publishes `symbol`: {@link FLAG_PUBLISHED}
610
+ * when under the symbol's own name (or as a global), and
611
+ * {@link FLAG_MODULE_DEFAULT} when as the module's `export =` or `default`.
612
+ * The module is the nearest ambient `declare module "…"` or the file.
613
+ */
614
+ publication(symbol, declaration) {
615
+ let container = declaration.parent;
616
+ while (container && !ts.isSourceFile(container) && !ts.isModuleBlock(container)) {
617
+ // Anything but a statement list between the declaration and its module
618
+ // (a class, interface, type or object literal, a function body) makes
619
+ // it a member or a local.
620
+ if (!ts.isVariableDeclarationList(container) && !ts.isVariableStatement(container))
621
+ return 0;
622
+ container = container.parent;
623
+ }
624
+ if (!container)
625
+ return 0;
626
+ const block = ts.isModuleBlock(container) ? container.parent : undefined;
627
+ if (block && block.flags & ts.NodeFlags.GlobalAugmentation)
628
+ return FLAG_PUBLISHED;
629
+ // A namespace's member is published when the module its file is publishes it.
630
+ if (block && !ts.isStringLiteral(block.name))
631
+ return this.publishedByExports(symbol, declaration);
632
+ const moduleSymbol = this.checker.getSymbolAtLocation(block ? block.name : container);
633
+ if (moduleSymbol)
634
+ return this.publishedIn(moduleSymbol, symbol);
635
+ // A script's top-level declaration is a global.
636
+ return ts.isSourceFile(container) && !ts.isExternalModule(container) ? FLAG_PUBLISHED : 0;
637
+ }
638
+ /** A namespace member, found among the exports of the module it sits in. */
639
+ publishedByExports(symbol, declaration) {
640
+ for (let node = declaration.parent; node; node = node.parent) {
641
+ if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) {
642
+ const moduleSymbol = this.checker.getSymbolAtLocation(node.name);
643
+ return moduleSymbol ? this.publishedIn(moduleSymbol, symbol) : 0;
644
+ }
645
+ if (ts.isSourceFile(node)) {
646
+ const moduleSymbol = this.checker.getSymbolAtLocation(node);
647
+ return moduleSymbol ? this.publishedIn(moduleSymbol, symbol) & FLAG_PUBLISHED : 0;
648
+ }
649
+ }
650
+ return 0;
651
+ }
652
+ publishedIn(moduleSymbol, symbol) {
653
+ let exports = this.moduleExports.get(moduleSymbol);
654
+ if (!exports) {
655
+ const resolve = (s) => s.flags & ts.SymbolFlags.Alias ? this.checker.getAliasedSymbol(s) : s;
656
+ const byName = new Map();
657
+ for (const exported of this.checker.getExportsOfModule(moduleSymbol)) {
658
+ byName.set(exported.getName(), resolve(exported));
659
+ }
660
+ const wholes = [ts.InternalSymbolName.ExportEquals, ts.InternalSymbolName.Default]
661
+ .map((key) => moduleSymbol.exports?.get(key))
662
+ .filter((own) => own !== undefined)
663
+ .map(resolve);
664
+ exports = { byName, wholes };
665
+ this.moduleExports.set(moduleSymbol, exports);
666
+ }
667
+ return ((exports.byName.get(symbol.getName()) === symbol ? FLAG_PUBLISHED : 0) |
668
+ (exports.wholes.includes(symbol) ? FLAG_MODULE_DEFAULT : 0));
669
+ }
670
+ /** The type of the object a `member` or `call` row's name is read on, if the name is read on one. */
671
+ receiverOf(kind, node) {
672
+ if (kind !== 'member' && kind !== 'call')
673
+ return undefined;
674
+ const access = nameNodeOf(kind, node)?.parent;
675
+ if (!access || !(ts.isPropertyAccessExpression(access) || ts.isElementAccessExpression(access))) {
676
+ return undefined;
677
+ }
678
+ return this.checker.getTypeAtLocation(access.expression);
679
+ }
680
+ /**
681
+ * The `decl` rows of a `call` or `member` row (spans unset): one per
682
+ * declaration the receiver's union constituents give the member, when they
683
+ * give more than one (carrick#2448). A constituent's declaration is its
684
+ * member's ({@link attributedDeclaration}), or, for a member with none, the
685
+ * constituent type's own ({@link typeDeclaration}). Two instantiations of
686
+ * one generic class, or one class and `undefined`, give one, and no row.
687
+ * A throw writes one row flagged {@link FLAG_CHECKER_THREW}.
688
+ */
689
+ declRows(kind, node) {
690
+ const found = new Set();
691
+ let name;
692
+ try {
693
+ const symbol = this.symbolOf(kind, node);
694
+ const receiver = this.receiverOf(kind, node);
695
+ if (!symbol || !receiver || !receiver.isUnion())
696
+ return [];
697
+ name = ts.symbolName(symbol);
698
+ for (const constituent of receiver.types) {
699
+ const member = constituent.getProperty(symbol.name);
700
+ if (!member)
701
+ continue;
702
+ const declaration = attributedDeclaration(() => this.program, member.declarations ?? []) ?? this.typeDeclaration(constituent);
703
+ if (declaration)
704
+ found.add(declaration);
705
+ }
706
+ }
707
+ catch {
708
+ return [['decl', 0, 0, null, null, null, null, null, FLAG_CHECKER_THREW]];
709
+ }
710
+ if (found.size < 2)
711
+ return [];
712
+ if (found.size > IMPL_CAP)
713
+ return [['decl', 0, 0, null, null, null, null, null, FLAG_TOO_MANY]];
714
+ const rows = [...found].map((declaration) => {
715
+ const file = declaration.getSourceFile();
716
+ const start = declaration.getStart(file);
717
+ const { declFile, declLine, declPackage } = this.located(file, start);
718
+ const flags = declFile === null ? FLAG_DECLARATION_UNNAMED : 0;
719
+ return { start, row: ['decl', 0, 0, name, declFile, declLine, declPackage, null, flags] };
720
+ });
721
+ rows.sort((a, b) => compareNames(a.row[4], b.row[4]) || a.start - b.start);
722
+ return rows.map(({ row }) => row);
723
+ }
583
724
  /** The row's symbol, following aliases to a target that has a declaration. */
584
725
  symbolOf(kind, node) {
585
726
  let symbol;
@@ -778,6 +919,7 @@ function nameNodeOf(kind, node) {
778
919
  case 'ident':
779
920
  return node;
780
921
  case 'impl':
922
+ case 'decl':
781
923
  return undefined;
782
924
  case 'jsx':
783
925
  // The tag: `<Badge>`, `<ui.Button>`, `<this.Panel>`; a namespaced