• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / js-slang / 23995741899

05 Apr 2026 06:14AM UTC coverage: 77.093% (+0.002%) from 77.091%
23995741899

push

github

web-flow
Upgrade to TypeScript 6 and Prettier improvements (#1936)

* Upgrade TypeScript to v6

* Fix import source

* Fix tsconfig

* Fix preexisting type errors

* Remove scm-slang

* Bump node types

* Fix tsconfig

* Fix node types specifier

* Enable trailing commas

* Enable semicolons

* Check and commit files with changed line numbers

* Update Yarn to 4.13.0

* Remove unneeded sicp package deps

3112 of 4282 branches covered (72.68%)

Branch coverage included in aggregate %.

3761 of 5218 new or added lines in 152 files covered. (72.08%)

26 existing lines in 9 files now uncovered.

7136 of 9011 relevant lines covered (79.19%)

175254.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.17
/src/modules/preprocessor/analyzer.ts
1
import type es from 'estree';
2
import { partition } from 'lodash';
3

4
import type { Context } from '../../types';
5
import assert from '../../utils/assert';
6
import {
7
  getIdsFromDeclaration,
8
  getImportedName,
9
  getModuleDeclarationSource,
10
} from '../../utils/ast/helpers';
11
import { isModuleDeclaration, isNamespaceSpecifier } from '../../utils/ast/typeGuards';
12
import Dict, { ArrayMap } from '../../utils/dict';
13
import {
14
  DuplicateImportNameError,
15
  UndefinedDefaultImportError,
16
  UndefinedImportError,
17
  UndefinedNamespaceImportError,
18
} from '../errors';
19
import { isSourceModule } from '../utils';
20

21
export const defaultAnalysisOptions: ImportAnalysisOptions = {
58✔
22
  allowUndefinedImports: false,
23
  throwOnDuplicateNames: true,
24
};
25

26
/**
27
 * Options to configure import analysis
28
 */
29
export type ImportAnalysisOptions = {
30
  /**
31
   * Set to true to allow trying to import symbols that aren't exported by
32
   * the module being imported
33
   */
34
  allowUndefinedImports: boolean;
35

36
  /**
37
   * Set to true to throw errors when different imported symbols are given
38
   * the same declared name
39
   */
40
  throwOnDuplicateNames: boolean;
41
};
42

43
/**
44
 * Import and Export analyzer:
45
 * - Checks for undefined imports
46
 * - Checks for different imports being given the same local name
47
 */
48
export default function analyzeImportsAndExports(
49
  programs: Record<string, es.Program>,
50
  entrypointFilePath: string,
51
  topoOrder: string[],
52
  { nativeStorage: { loadedModules } }: Context,
53
  options: Partial<ImportAnalysisOptions> = {},
1,161✔
54
) {
55
  const declaredNames = new Dict<
1,161✔
56
    string,
57
    ArrayMap<string, es.ImportDeclaration['specifiers'][number]>
58
  >();
59

60
  const moduleDocs: Record<string, Set<string>> = Object.fromEntries(
1,161✔
61
    Object.entries(loadedModules).map(([name, obj]) => [name, new Set(Object.keys(obj))]),
168✔
62
  );
63

64
  for (const sourceModule of [...topoOrder, entrypointFilePath]) {
1,161✔
65
    const program = programs[sourceModule];
1,375✔
66
    moduleDocs[sourceModule] = new Set();
1,375✔
67

68
    for (const node of program.body) {
1,375✔
69
      if (node.type === 'ExportDefaultDeclaration') {
2,159✔
70
        if (!options.allowUndefinedImports) {
19✔
71
          assert(
9✔
72
            !moduleDocs[sourceModule].has('default'),
73
            "Multiple default exports should've been caught by the parser",
74
          );
75
          moduleDocs[sourceModule].add('default');
9✔
76
        }
77
        continue;
19✔
78
      }
79

80
      if (node.type === 'ExportNamedDeclaration') {
2,140✔
81
        if (node.declaration) {
120✔
82
          if (!options.allowUndefinedImports) {
99✔
83
            const ids = getIdsFromDeclaration(node.declaration);
40✔
84
            ids.forEach(id => {
40✔
85
              moduleDocs[sourceModule].add(id.name);
40✔
86
            });
87
          }
88
          continue;
99✔
89
        }
90

91
        for (const spec of node.specifiers) {
21✔
92
          moduleDocs[sourceModule].add(spec.exported.name);
22✔
93
        }
94

95
        if (!node.source) continue;
21✔
96
      } else if (!isModuleDeclaration(node)) {
2,020✔
97
        continue;
1,716✔
98
      }
99

100
      const dstModule = getModuleDeclarationSource(node);
323✔
101
      const dstModuleDocs = moduleDocs[dstModule];
323✔
102

103
      if (node.type === 'ExportAllDeclaration') {
323✔
104
        if (!options.allowUndefinedImports) {
15✔
105
          if (dstModuleDocs.size === 0) throw new UndefinedNamespaceImportError(dstModule, node);
7✔
106

107
          if (node.exported) {
5!
NEW
108
            moduleDocs[sourceModule].add(node.exported.name);
×
109
          } else {
110
            for (const each of dstModuleDocs) {
5✔
111
              if (each === 'default') {
7✔
112
                // ExportAllDeclarations do not implicitly reexport default exports
113
                continue;
3✔
114
              }
115

116
              moduleDocs[sourceModule].add(each);
4✔
117
            }
118
          }
119
        }
120
        continue;
13✔
121
      }
122

123
      for (const spec of node.specifiers) {
308✔
124
        if (
344✔
125
          options.throwOnDuplicateNames &&
470✔
126
          spec.type !== 'ExportSpecifier' &&
127
          isSourceModule(dstModule)
128
        ) {
129
          const declaredName = spec.local.name;
57✔
130
          declaredNames.setdefault(declaredName, new ArrayMap()).add(dstModule, spec);
57✔
131
        }
132

133
        if (options.allowUndefinedImports) continue;
344✔
134

135
        if (spec.type === 'ImportNamespaceSpecifier') {
73✔
136
          if (dstModuleDocs.size === 0) throw new UndefinedNamespaceImportError(dstModule, spec);
4✔
137
          continue;
3✔
138
        }
139

140
        const importedName = getImportedName(spec);
69✔
141

142
        if (!dstModuleDocs.has(importedName)) {
69✔
143
          if (importedName === 'default') throw new UndefinedDefaultImportError(dstModule, spec);
17✔
144
          throw new UndefinedImportError(importedName, dstModule, spec);
7✔
145
        }
146
      }
147
    }
148
  }
149

150
  if (!options.throwOnDuplicateNames) return;
1,141✔
151

152
  // Because of the way the preprocessor works, different imports with the same declared name
153
  // will cause errors
154
  // There are two conditions we need to check:
155
  // 1. Two different symbols from the same module are declared with the same name:
156
  // import { a as x } from 'one_module'; AND import { b as x } from 'one_module';
157
  // 2. Two different symbols from different modules are declared with the same name:
158
  // import { a } from 'one_module'; AND import { b as a } from 'another_module';
159
  for (const [localName, moduleToSpecifierMap] of declaredNames) {
28✔
160
    if (moduleToSpecifierMap.size > 1) {
32✔
161
      // This means that two imports from different modules have the same
162
      // declared name
163
      const nodes = moduleToSpecifierMap.flatMap((_, v) => v);
10✔
164
      throw new DuplicateImportNameError(localName, nodes);
5✔
165
    }
166

167
    const [[, specifiers]] = moduleToSpecifierMap;
27✔
168
    const [namespaceSpecifiers, regularSpecifiers] = partition<
27✔
169
      es.ImportDeclaration['specifiers'][number],
170
      es.ImportNamespaceSpecifier
171
    >(specifiers, isNamespaceSpecifier);
172

173
    // For the given local name, it can only represent one imported name from
174
    // the module. Collect specifiers referring to the same export.
175
    const importedNames = new Set(regularSpecifiers.map(getImportedName));
27✔
176

177
    if (
27✔
178
      (namespaceSpecifiers.length > 0 && regularSpecifiers.length > 0) ||
56✔
179
      importedNames.size > 1
180
    ) {
181
      // This means that there is more than one unique export being given the same
182
      // local name
183
      const specs = [...regularSpecifiers, ...namespaceSpecifiers];
5✔
184
      throw new DuplicateImportNameError(localName, specs);
5✔
185
    }
186
  }
187
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc