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

benmosher / eslint-plugin-import / #28399482

31 Aug 2017 08:48AM UTC coverage: 0.0% (-71.0%) from 71.019%
#28399482

push

ljharb
Handle unresolved js import when type is exported with the same name

0 of 1985 branches covered (0.0%)

0 of 8 new or added lines in 2 files covered. (0.0%)

2289 existing lines in 51 files now uncovered.

0 of 3012 relevant lines covered (0.0%)

0.0 hits per line

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

0.0
/src/ExportMap.js
1
import fs from 'fs';
2

3
import doctrine from 'doctrine';
4

5
import debug from 'debug';
6

7
import { SourceCode } from 'eslint';
8

9
import parse from 'eslint-module-utils/parse';
10
import resolve from 'eslint-module-utils/resolve';
11
import isIgnored, { hasValidExtension } from 'eslint-module-utils/ignore';
12

13
import { hashObject } from 'eslint-module-utils/hash';
14
import * as unambiguous from 'eslint-module-utils/unambiguous';
15

16
import { tsConfigLoader } from 'tsconfig-paths/lib/tsconfig-loader';
17

18
import includes from 'array-includes';
19

20
let parseConfigFileTextToJson;
21

UNCOV
22
const log = debug('eslint-plugin-import:ExportMap');
×
23

UNCOV
24
const exportCache = new Map();
×
UNCOV
25
const tsConfigCache = new Map();
×
26

27
export default class ExportMap {
28
  constructor(path) {
UNCOV
29
    this.path = path;
×
UNCOV
30
    this.namespace = new Map();
×
31
    // todo: restructure to key on path, value is resolver + map of names
UNCOV
32
    this.reexports = new Map();
×
33
    /**
34
     * star-exports
35
     * @type {Set} of () => ExportMap
36
     */
UNCOV
37
    this.dependencies = new Set();
×
38
    /**
39
     * dependencies of this module that are not explicitly re-exported
40
     * @type {Map} from path = () => ExportMap
41
     */
UNCOV
42
    this.imports = new Map();
×
UNCOV
43
    this.errors = [];
×
44
  }
45

UNCOV
46
  get hasDefault() { return this.get('default') != null; } // stronger than this.has
×
47

48
  get size() {
UNCOV
49
    let size = this.namespace.size + this.reexports.size;
×
UNCOV
50
    this.dependencies.forEach(dep => {
×
UNCOV
51
      const d = dep();
×
52
      // CJS / ignored dependencies won't exist (#717)
UNCOV
53
      if (d == null) return;
×
UNCOV
54
      size += d.size;
×
55
    });
UNCOV
56
    return size;
×
57
  }
58

59
  /**
60
   * Note that this does not check explicitly re-exported names for existence
61
   * in the base namespace, but it will expand all `export * from '...'` exports
62
   * if not found in the explicit namespace.
63
   * @param  {string}  name
64
   * @return {Boolean} true if `name` is exported by this module.
65
   */
66
  has(name) {
UNCOV
67
    if (this.namespace.has(name)) return true;
×
UNCOV
68
    if (this.reexports.has(name)) return true;
×
69

70
    // default exports must be explicitly re-exported (#328)
UNCOV
71
    if (name !== 'default') {
×
UNCOV
72
      for (const dep of this.dependencies) {
×
UNCOV
73
        const innerMap = dep();
×
74

75
        // todo: report as unresolved?
UNCOV
76
        if (!innerMap) continue;
×
77

UNCOV
78
        if (innerMap.has(name)) return true;
×
79
      }
80
    }
81

UNCOV
82
    return false;
×
83
  }
84

85
  /**
86
   * ensure that imported name fully resolves.
87
   * @param  {string} name
88
   * @return {{ found: boolean, path: ExportMap[] }}
89
   */
90
  hasDeep(name) {
UNCOV
91
    if (this.namespace.has(name)) return { found: true, path: [this] };
×
92

UNCOV
93
    if (this.reexports.has(name)) {
×
UNCOV
94
      const reexports = this.reexports.get(name);
×
UNCOV
95
      const imported = reexports.getImport();
×
96

97
      // if import is ignored, return explicit 'null'
UNCOV
98
      if (imported == null) return { found: true, path: [this] };
×
99

100
      // safeguard against cycles, only if name matches
UNCOV
101
      if (imported.path === this.path && reexports.local === name) {
×
UNCOV
102
        return { found: false, path: [this] };
×
103
      }
104

UNCOV
105
      const deep = imported.hasDeep(reexports.local);
×
UNCOV
106
      deep.path.unshift(this);
×
107

UNCOV
108
      return deep;
×
109
    }
110

111

112
    // default exports must be explicitly re-exported (#328)
UNCOV
113
    if (name !== 'default') {
×
UNCOV
114
      for (const dep of this.dependencies) {
×
UNCOV
115
        const innerMap = dep();
×
UNCOV
116
        if (innerMap == null) return { found: true, path: [this] };
×
117
        // todo: report as unresolved?
UNCOV
118
        if (!innerMap) continue;
×
119

120
        // safeguard against cycles
UNCOV
121
        if (innerMap.path === this.path) continue;
×
122

UNCOV
123
        const innerValue = innerMap.hasDeep(name);
×
UNCOV
124
        if (innerValue.found) {
×
UNCOV
125
          innerValue.path.unshift(this);
×
UNCOV
126
          return innerValue;
×
127
        }
128
      }
129
    }
130

UNCOV
131
    return { found: false, path: [this] };
×
132
  }
133

134
  get(name) {
UNCOV
135
    if (this.namespace.has(name)) return this.namespace.get(name);
×
136

UNCOV
137
    if (this.reexports.has(name)) {
×
UNCOV
138
      const reexports = this.reexports.get(name);
×
UNCOV
139
      const imported = reexports.getImport();
×
140

141
      // if import is ignored, return explicit 'null'
UNCOV
142
      if (imported == null) return null;
×
143

144
      // safeguard against cycles, only if name matches
UNCOV
145
      if (imported.path === this.path && reexports.local === name) return undefined;
×
146

UNCOV
147
      return imported.get(reexports.local);
×
148
    }
149

150
    // default exports must be explicitly re-exported (#328)
UNCOV
151
    if (name !== 'default') {
×
UNCOV
152
      for (const dep of this.dependencies) {
×
UNCOV
153
        const innerMap = dep();
×
154
        // todo: report as unresolved?
UNCOV
155
        if (!innerMap) continue;
×
156

157
        // safeguard against cycles
UNCOV
158
        if (innerMap.path === this.path) continue;
×
159

UNCOV
160
        const innerValue = innerMap.get(name);
×
UNCOV
161
        if (innerValue !== undefined) return innerValue;
×
162
      }
163
    }
164

UNCOV
165
    return undefined;
×
166
  }
167

168
  forEach(callback, thisArg) {
UNCOV
169
    this.namespace.forEach((v, n) =>
×
UNCOV
170
      callback.call(thisArg, v, n, this));
×
171

UNCOV
172
    this.reexports.forEach((reexports, name) => {
×
UNCOV
173
      const reexported = reexports.getImport();
×
174
      // can't look up meta for ignored re-exports (#348)
UNCOV
175
      callback.call(thisArg, reexported && reexported.get(reexports.local), name, this);
×
176
    });
177

UNCOV
178
    this.dependencies.forEach(dep => {
×
UNCOV
179
      const d = dep();
×
180
      // CJS / ignored dependencies won't exist (#717)
UNCOV
181
      if (d == null) return;
×
182

UNCOV
183
      d.forEach((v, n) =>
×
UNCOV
184
        n !== 'default' && callback.call(thisArg, v, n, this));
×
185
    });
186
  }
187

188
  // todo: keys, values, entries?
189

190
  reportErrors(context, declaration) {
UNCOV
191
    context.report({
×
192
      node: declaration.source,
193
      message: `Parse errors in imported module '${declaration.source.value}': ` +
194
                  `${this.errors
UNCOV
195
                    .map(e => `${e.message} (${e.lineNumber}:${e.column})`)
×
196
                    .join(', ')}`,
197
    });
198
  }
199
}
200

201
/**
202
 * parse docs from the first node that has leading comments
203
 */
204
function captureDoc(source, docStyleParsers, ...nodes) {
UNCOV
205
  const metadata = {};
×
206

207
  // 'some' short-circuits on first 'true'
UNCOV
208
  nodes.some(n => {
×
UNCOV
209
    try {
×
210

211
      let leadingComments;
212

213
      // n.leadingComments is legacy `attachComments` behavior
UNCOV
214
      if ('leadingComments' in n) {
×
UNCOV
215
        leadingComments = n.leadingComments;
×
UNCOV
216
      } else if (n.range) {
×
UNCOV
217
        leadingComments = source.getCommentsBefore(n);
×
218
      }
219

UNCOV
220
      if (!leadingComments || leadingComments.length === 0) return false;
×
221

UNCOV
222
      for (const name in docStyleParsers) {
×
UNCOV
223
        const doc = docStyleParsers[name](leadingComments);
×
UNCOV
224
        if (doc) {
×
UNCOV
225
          metadata.doc = doc;
×
226
        }
227
      }
228

UNCOV
229
      return true;
×
230
    } catch (err) {
UNCOV
231
      return false;
×
232
    }
233
  });
234

UNCOV
235
  return metadata;
×
236
}
237

UNCOV
238
const availableDocStyleParsers = {
×
239
  jsdoc: captureJsDoc,
240
  tomdoc: captureTomDoc,
241
};
242

243
/**
244
 * parse JSDoc from leading comments
245
 * @param {object[]} comments
246
 * @return {{ doc: object }}
247
 */
248
function captureJsDoc(comments) {
249
  let doc;
250

251
  // capture XSDoc
UNCOV
252
  comments.forEach(comment => {
×
253
    // skip non-block comments
UNCOV
254
    if (comment.type !== 'Block') return;
×
UNCOV
255
    try {
×
UNCOV
256
      doc = doctrine.parse(comment.value, { unwrap: true });
×
257
    } catch (err) {
258
      /* don't care, for now? maybe add to `errors?` */
259
    }
260
  });
261

UNCOV
262
  return doc;
×
263
}
264

265
/**
266
  * parse TomDoc section from comments
267
  */
268
function captureTomDoc(comments) {
269
  // collect lines up to first paragraph break
UNCOV
270
  const lines = [];
×
UNCOV
271
  for (let i = 0; i < comments.length; i++) {
×
UNCOV
272
    const comment = comments[i];
×
UNCOV
273
    if (comment.value.match(/^\s*$/)) break;
×
UNCOV
274
    lines.push(comment.value.trim());
×
275
  }
276

277
  // return doctrine-like object
UNCOV
278
  const statusMatch = lines.join(' ').match(/^(Public|Internal|Deprecated):\s*(.+)/);
×
UNCOV
279
  if (statusMatch) {
×
UNCOV
280
    return {
×
281
      description: statusMatch[2],
282
      tags: [{
283
        title: statusMatch[1].toLowerCase(),
284
        description: statusMatch[2],
285
      }],
286
    };
287
  }
288
}
289

UNCOV
290
const supportedImportTypes = new Set(['ImportDefaultSpecifier', 'ImportNamespaceSpecifier']);
×
291

UNCOV
292
ExportMap.get = function (source, context) {
×
UNCOV
293
  const path = resolve(source, context);
×
UNCOV
294
  if (path == null) return null;
×
295

UNCOV
296
  return ExportMap.for(childContext(path, context));
×
297
};
298

UNCOV
299
ExportMap.for = function (context) {
×
UNCOV
300
  const { path } = context;
×
301

UNCOV
302
  const cacheKey = hashObject(context).digest('hex');
×
UNCOV
303
  let exportMap = exportCache.get(cacheKey);
×
304

305
  // return cached ignore
UNCOV
306
  if (exportMap === null) return null;
×
307

UNCOV
308
  const stats = fs.statSync(path);
×
UNCOV
309
  if (exportMap != null) {
×
310
    // date equality check
UNCOV
311
    if (exportMap.mtime - stats.mtime === 0) {
×
UNCOV
312
      return exportMap;
×
313
    }
314
    // future: check content equality?
315
  }
316

317
  // check valid extensions first
UNCOV
318
  if (!hasValidExtension(path, context)) {
×
UNCOV
319
    exportCache.set(cacheKey, null);
×
UNCOV
320
    return null;
×
321
  }
322

323
  // check for and cache ignore
UNCOV
324
  if (isIgnored(path, context)) {
×
UNCOV
325
    log('ignored path due to ignore settings:', path);
×
UNCOV
326
    exportCache.set(cacheKey, null);
×
UNCOV
327
    return null;
×
328
  }
329

UNCOV
330
  const content = fs.readFileSync(path, { encoding: 'utf8' });
×
331

332
  // check for and cache unambiguous modules
UNCOV
333
  if (!unambiguous.test(content)) {
×
UNCOV
334
    log('ignored path due to unambiguous regex:', path);
×
UNCOV
335
    exportCache.set(cacheKey, null);
×
UNCOV
336
    return null;
×
337
  }
338

UNCOV
339
  log('cache miss', cacheKey, 'for path', path);
×
UNCOV
340
  exportMap = ExportMap.parse(path, content, context);
×
341

342
  // ambiguous modules return null
UNCOV
343
  if (exportMap == null) return null;
×
344

UNCOV
345
  exportMap.mtime = stats.mtime;
×
346

UNCOV
347
  exportCache.set(cacheKey, exportMap);
×
UNCOV
348
  return exportMap;
×
349
};
350

351

352
ExportMap.parse = function (path, content, context) {
×
UNCOV
353
  const m = new ExportMap(path);
×
354

355
  let ast;
UNCOV
356
  try {
×
UNCOV
357
    ast = parse(path, content, context);
×
358
  } catch (err) {
UNCOV
359
    log('parse error:', path, err);
×
UNCOV
360
    m.errors.push(err);
×
UNCOV
361
    return m; // can't continue
×
362
  }
363

UNCOV
364
  if (!unambiguous.isModule(ast)) return null;
×
365

UNCOV
366
  const docstyle = (context.settings && context.settings['import/docstyle']) || ['jsdoc'];
×
UNCOV
367
  const docStyleParsers = {};
×
UNCOV
368
  docstyle.forEach(style => {
×
UNCOV
369
    docStyleParsers[style] = availableDocStyleParsers[style];
×
370
  });
371

372
  // attempt to collect module doc
UNCOV
373
  if (ast.comments) {
×
UNCOV
374
    ast.comments.some(c => {
×
UNCOV
375
      if (c.type !== 'Block') return false;
×
UNCOV
376
      try {
×
UNCOV
377
        const doc = doctrine.parse(c.value, { unwrap: true });
×
UNCOV
378
        if (doc.tags.some(t => t.title === 'module')) {
×
UNCOV
379
          m.doc = doc;
×
UNCOV
380
          return true;
×
381
        }
382
      } catch (err) { /* ignore */ }
UNCOV
383
      return false;
×
384
    });
385
  }
386

UNCOV
387
  const namespaces = new Map();
×
388

389
  function remotePath(value) {
UNCOV
390
    return resolve.relative(value, path, context.settings);
×
391
  }
392

393
  function resolveImport(value) {
UNCOV
394
    const rp = remotePath(value);
×
UNCOV
395
    if (rp == null) return null;
×
UNCOV
396
    return ExportMap.for(childContext(rp, context));
×
397
  }
398

399
  function getNamespace(identifier) {
UNCOV
400
    if (!namespaces.has(identifier.name)) return;
×
401

UNCOV
402
    return function () {
×
UNCOV
403
      return resolveImport(namespaces.get(identifier.name));
×
404
    };
405
  }
406

407
  function addNamespace(object, identifier) {
UNCOV
408
    const nsfn = getNamespace(identifier);
×
UNCOV
409
    if (nsfn) {
×
UNCOV
410
      Object.defineProperty(object, 'namespace', { get: nsfn });
×
411
    }
412

UNCOV
413
    return object;
×
414
  }
415

416
  function captureDependency({ source }, isOnlyImportingTypes, importedSpecifiers = new Set()) {
×
UNCOV
417
    if (source == null) return null;
×
418

UNCOV
419
    const p = remotePath(source.value);
×
UNCOV
420
    if (p == null) return null;
×
421

UNCOV
422
    const declarationMetadata = {
×
423
      // capturing actual node reference holds full AST in memory!
424
      source: { value: source.value, loc: source.loc },
425
      isOnlyImportingTypes,
426
      importedSpecifiers,
427
    };
428

UNCOV
429
    const existing = m.imports.get(p);
×
UNCOV
430
    if (existing != null) {
×
UNCOV
431
      existing.declarations.add(declarationMetadata);
×
UNCOV
432
      return existing.getter;
×
433
    }
434

UNCOV
435
    const getter = thunkFor(p, context);
×
UNCOV
436
    m.imports.set(p, { getter, declarations: new Set([declarationMetadata]) });
×
UNCOV
437
    return getter;
×
438
  }
439

UNCOV
440
  const source = makeSourceCode(content, ast);
×
441

442
  function readTsConfig() {
UNCOV
443
    const tsConfigInfo = tsConfigLoader({
×
444
      cwd:
445
        (context.parserOptions && context.parserOptions.tsconfigRootDir) ||
×
446
        process.cwd(),
UNCOV
447
      getEnv: (key) => process.env[key],
×
448
    });
UNCOV
449
    try {
×
UNCOV
450
      if (tsConfigInfo.tsConfigPath !== undefined) {
×
UNCOV
451
        const jsonText = fs.readFileSync(tsConfigInfo.tsConfigPath).toString();
×
UNCOV
452
        if (!parseConfigFileTextToJson) {
×
453
          // this is because projects not using TypeScript won't have typescript installed
UNCOV
454
          ({ parseConfigFileTextToJson } = require('typescript'));
×
455
        }
UNCOV
456
        return parseConfigFileTextToJson(tsConfigInfo.tsConfigPath, jsonText).config;
×
457
      }
458
    } catch (e) {
459
      // Catch any errors
460
    }
461

UNCOV
462
    return null;
×
463
  }
464

465
  function isEsModuleInterop() {
UNCOV
466
    const cacheKey = hashObject({
×
467
      tsconfigRootDir: context.parserOptions && context.parserOptions.tsconfigRootDir,
×
468
    }).digest('hex');
UNCOV
469
    let tsConfig = tsConfigCache.get(cacheKey);
×
UNCOV
470
    if (typeof tsConfig === 'undefined') {
×
UNCOV
471
      tsConfig = readTsConfig();
×
UNCOV
472
      tsConfigCache.set(cacheKey, tsConfig);
×
473
    }
474

UNCOV
475
    return tsConfig !== null ? tsConfig.compilerOptions.esModuleInterop : false;
×
476
  }
477

UNCOV
478
  ast.body.forEach(function (n) {
×
UNCOV
479
    if (n.type === 'ExportDefaultDeclaration') {
×
UNCOV
480
      const exportMeta = captureDoc(source, docStyleParsers, n);
×
UNCOV
481
      if (n.declaration.type === 'Identifier') {
×
UNCOV
482
        addNamespace(exportMeta, n.declaration);
×
483
      }
UNCOV
484
      m.namespace.set('default', exportMeta);
×
UNCOV
485
      return;
×
486
    }
487

UNCOV
488
    if (n.type === 'ExportAllDeclaration') {
×
UNCOV
489
      const getter = captureDependency(n, n.exportKind === 'type');
×
UNCOV
490
      if (getter) m.dependencies.add(getter);
×
UNCOV
491
      return;
×
492
    }
493

494
    // capture namespaces in case of later export
UNCOV
495
    if (n.type === 'ImportDeclaration') {
×
496
      // import type { Foo } (TS and Flow)
UNCOV
497
      const declarationIsType = n.importKind === 'type';
×
UNCOV
498
      let isOnlyImportingTypes = declarationIsType;
×
UNCOV
499
      const importedSpecifiers = new Set();
×
UNCOV
500
      n.specifiers.forEach(specifier => {
×
UNCOV
501
        if (supportedImportTypes.has(specifier.type)) {
×
UNCOV
502
          importedSpecifiers.add(specifier.type);
×
503
        }
UNCOV
504
        if (specifier.type === 'ImportSpecifier') {
×
UNCOV
505
          importedSpecifiers.add(specifier.imported.name);
×
506
        }
507

508
        // import { type Foo } (Flow)
UNCOV
509
        if (!declarationIsType) {
×
UNCOV
510
          isOnlyImportingTypes = specifier.importKind === 'type';
×
511
        }
512
      });
UNCOV
513
      captureDependency(n, isOnlyImportingTypes, importedSpecifiers);
×
514

UNCOV
515
      const ns = n.specifiers.find(s => s.type === 'ImportNamespaceSpecifier');
×
UNCOV
516
      if (ns) {
×
UNCOV
517
        namespaces.set(ns.local.name, n.source.value);
×
518
      }
UNCOV
519
      return;
×
520
    }
521

UNCOV
522
    if (n.type === 'ExportNamedDeclaration') {
×
523
      // capture declaration
UNCOV
524
      if (n.declaration != null) {
×
UNCOV
525
        switch (n.declaration.type) {
×
526
        case 'FunctionDeclaration':
527
        case 'ClassDeclaration':
528
        case 'TypeAlias': // flowtype with babel-eslint parser
529
        case 'InterfaceDeclaration':
530
        case 'DeclareFunction':
531
        case 'TSDeclareFunction':
532
        case 'TSEnumDeclaration':
533
        case 'TSTypeAliasDeclaration':
534
        case 'TSInterfaceDeclaration':
535
        case 'TSAbstractClassDeclaration':
536
        case 'TSModuleDeclaration': {
NEW
537
          const meta = captureDoc(docStyleParsers, n);
×
NEW
538
          meta.exportKind = n.exportKind;
×
NEW
539
          m.namespace.set(n.declaration.id.name, meta);
×
UNCOV
540
          break;
×
541
        }
542
        case 'VariableDeclaration':
UNCOV
543
          n.declaration.declarations.forEach((d) =>
×
UNCOV
544
            recursivePatternCapture(d.id,
×
UNCOV
545
              id => m.namespace.set(id.name, captureDoc(source, docStyleParsers, d, n))));
×
UNCOV
546
          break;
×
547
        }
548
      }
549

UNCOV
550
      const nsource = n.source && n.source.value;
×
UNCOV
551
      n.specifiers.forEach((s) => {
×
UNCOV
552
        const exportMeta = {};
×
553
        let local;
554

UNCOV
555
        switch (s.type) {
×
556
        case 'ExportDefaultSpecifier':
UNCOV
557
          if (!n.source) return;
×
UNCOV
558
          local = 'default';
×
UNCOV
559
          break;
×
560
        case 'ExportNamespaceSpecifier':
UNCOV
561
          m.namespace.set(s.exported.name, Object.defineProperty(exportMeta, 'namespace', {
×
UNCOV
562
            get() { return resolveImport(nsource); },
×
563
          }));
UNCOV
564
          return;
×
565
        case 'ExportSpecifier':
UNCOV
566
          if (!n.source) {
×
UNCOV
567
            m.namespace.set(s.exported.name, addNamespace(exportMeta, s.local));
×
UNCOV
568
            return;
×
569
          }
570
          // else falls through
571
        default:
UNCOV
572
          local = s.local.name;
×
UNCOV
573
          break;
×
574
        }
575

576
        // todo: JSDoc
UNCOV
577
        m.reexports.set(s.exported.name, { local, getImport: () => resolveImport(nsource) });
×
578
      });
579
    }
580

UNCOV
581
    const isEsModuleInteropTrue = isEsModuleInterop();
×
582

UNCOV
583
    const exports = ['TSExportAssignment'];
×
UNCOV
584
    if (isEsModuleInteropTrue) {
×
UNCOV
585
      exports.push('TSNamespaceExportDeclaration');
×
586
    }
587

588
    // This doesn't declare anything, but changes what's being exported.
UNCOV
589
    if (includes(exports, n.type)) {
×
UNCOV
590
      const exportedName = n.type === 'TSNamespaceExportDeclaration'
×
591
        ? n.id.name
592
        : (n.expression && n.expression.name || (n.expression.id && n.expression.id.name) || null);
×
UNCOV
593
      const declTypes = [
×
594
        'VariableDeclaration',
595
        'ClassDeclaration',
596
        'TSDeclareFunction',
597
        'TSEnumDeclaration',
598
        'TSTypeAliasDeclaration',
599
        'TSInterfaceDeclaration',
600
        'TSAbstractClassDeclaration',
601
        'TSModuleDeclaration',
602
      ];
UNCOV
603
      const exportedDecls = ast.body.filter(({ type, id, declarations }) => includes(declTypes, type) && (
×
UNCOV
604
        (id && id.name === exportedName) || (declarations && declarations.find((d) => d.id.name === exportedName))
×
605
      ));
UNCOV
606
      if (exportedDecls.length === 0) {
×
607
        // Export is not referencing any local declaration, must be re-exporting
UNCOV
608
        m.namespace.set('default', captureDoc(source, docStyleParsers, n));
×
UNCOV
609
        return;
×
610
      }
UNCOV
611
      if (isEsModuleInteropTrue) {
×
UNCOV
612
        m.namespace.set('default', {});
×
613
      }
UNCOV
614
      exportedDecls.forEach((decl) => {
×
UNCOV
615
        if (decl.type === 'TSModuleDeclaration') {
×
UNCOV
616
          if (decl.body && decl.body.type === 'TSModuleDeclaration') {
×
UNCOV
617
            m.namespace.set(decl.body.id.name, captureDoc(source, docStyleParsers, decl.body));
×
UNCOV
618
          } else if (decl.body && decl.body.body) {
×
UNCOV
619
            decl.body.body.forEach((moduleBlockNode) => {
×
620
              // Export-assignment exports all members in the namespace,
621
              // explicitly exported or not.
UNCOV
622
              const namespaceDecl = moduleBlockNode.type === 'ExportNamedDeclaration' ?
×
623
                moduleBlockNode.declaration :
624
                moduleBlockNode;
625

UNCOV
626
              if (!namespaceDecl) {
×
627
                // TypeScript can check this for us; we needn't
UNCOV
628
              } else if (namespaceDecl.type === 'VariableDeclaration') {
×
UNCOV
629
                namespaceDecl.declarations.forEach((d) =>
×
UNCOV
630
                  recursivePatternCapture(d.id, (id) => m.namespace.set(
×
631
                    id.name,
632
                    captureDoc(source, docStyleParsers, decl, namespaceDecl, moduleBlockNode)
633
                  ))
634
                );
635
              } else {
UNCOV
636
                m.namespace.set(
×
637
                  namespaceDecl.id.name,
638
                  captureDoc(source, docStyleParsers, moduleBlockNode));
639
              }
640
            });
641
          }
642
        } else {
643
          // Export as default
UNCOV
644
          m.namespace.set('default', captureDoc(source, docStyleParsers, decl));
×
645
        }
646
      });
647
    }
648
  });
649

UNCOV
650
  return m;
×
651
};
652

653
/**
654
 * The creation of this closure is isolated from other scopes
655
 * to avoid over-retention of unrelated variables, which has
656
 * caused memory leaks. See #1266.
657
 */
658
function thunkFor(p, context) {
UNCOV
659
  return () => ExportMap.for(childContext(p, context));
×
660
}
661

662

663
/**
664
 * Traverse a pattern/identifier node, calling 'callback'
665
 * for each leaf identifier.
666
 * @param  {node}   pattern
667
 * @param  {Function} callback
668
 * @return {void}
669
 */
670
export function recursivePatternCapture(pattern, callback) {
UNCOV
671
  switch (pattern.type) {
×
672
  case 'Identifier': // base case
UNCOV
673
    callback(pattern);
×
UNCOV
674
    break;
×
675

676
  case 'ObjectPattern':
UNCOV
677
    pattern.properties.forEach(p => {
×
UNCOV
678
      if (p.type === 'ExperimentalRestProperty' || p.type === 'RestElement') {
×
UNCOV
679
        callback(p.argument);
×
UNCOV
680
        return;
×
681
      }
UNCOV
682
      recursivePatternCapture(p.value, callback);
×
683
    });
UNCOV
684
    break;
×
685

686
  case 'ArrayPattern':
UNCOV
687
    pattern.elements.forEach((element) => {
×
UNCOV
688
      if (element == null) return;
×
UNCOV
689
      if (element.type === 'ExperimentalRestProperty' || element.type === 'RestElement') {
×
UNCOV
690
        callback(element.argument);
×
UNCOV
691
        return;
×
692
      }
UNCOV
693
      recursivePatternCapture(element, callback);
×
694
    });
UNCOV
695
    break;
×
696

697
  case 'AssignmentPattern':
UNCOV
698
    callback(pattern.left);
×
UNCOV
699
    break;
×
700
  }
701
}
702

703
/**
704
 * don't hold full context object in memory, just grab what we need.
705
 */
706
function childContext(path, context) {
UNCOV
707
  const { settings, parserOptions, parserPath } = context;
×
UNCOV
708
  return {
×
709
    settings,
710
    parserOptions,
711
    parserPath,
712
    path,
713
  };
714
}
715

716

717
/**
718
 * sometimes legacy support isn't _that_ hard... right?
719
 */
720
function makeSourceCode(text, ast) {
UNCOV
721
  if (SourceCode.length > 1) {
×
722
    // ESLint 3
UNCOV
723
    return new SourceCode(text, ast);
×
724
  } else {
725
    // ESLint 4, 5
UNCOV
726
    return new SourceCode({ text, ast });
×
727
  }
728
}
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