Skip to content

Commit 81dade6

Browse files
committed
add module exports to named exports
1 parent db471a8 commit 81dade6

File tree

2 files changed

+173
-10
lines changed

2 files changed

+173
-10
lines changed

src/ExportMap.js

+145-9
Original file line numberDiff line numberDiff line change
@@ -266,14 +266,14 @@ function captureTomDoc(comments) {
266266
}
267267
}
268268

269-
ExportMap.get = function (source, context) {
269+
ExportMap.get = function (source, context, options) {
270270
const path = resolve(source, context)
271271
if (path == null) return null
272272

273-
return ExportMap.for(childContext(path, context))
273+
return ExportMap.for(childContext(path, context), options)
274274
}
275275

276-
ExportMap.for = function (context) {
276+
ExportMap.for = function (context, options = {}) {
277277
const { path } = context
278278

279279
const cacheKey = hashObject(context).digest('hex')
@@ -300,14 +300,14 @@ ExportMap.for = function (context) {
300300
const content = fs.readFileSync(path, { encoding: 'utf8' })
301301

302302
// check for and cache ignore
303-
if (isIgnored(path, context) || !unambiguous.test(content)) {
303+
if (isIgnored(path, context) || (!options.useCommonjsExports && !unambiguous.test(content))) {
304304
log('ignored path due to unambiguous regex or ignore settings:', path)
305305
exportCache.set(cacheKey, null)
306306
return null
307307
}
308308

309309
log('cache miss', cacheKey, 'for path', path)
310-
exportMap = ExportMap.parse(path, content, context)
310+
exportMap = ExportMap.parse(path, content, context, options)
311311

312312
// ambiguous modules return null
313313
if (exportMap == null) return null
@@ -319,7 +319,9 @@ ExportMap.for = function (context) {
319319
}
320320

321321

322-
ExportMap.parse = function (path, content, context) {
322+
ExportMap.parse = function (path, content, context, options = {}) {
323+
log('using commonjs exports:', options.useCommonjsExports)
324+
323325
var m = new ExportMap(path)
324326

325327
try {
@@ -330,7 +332,7 @@ ExportMap.parse = function (path, content, context) {
330332
return m // can't continue
331333
}
332334

333-
if (!unambiguous.isModule(ast)) return null
335+
if (!options.useCommonjsExports && !unambiguous.isModule(ast)) return null
334336

335337
const docstyle = (context.settings && context.settings['import/docstyle']) || ['jsdoc']
336338
const docStyleParsers = {}
@@ -362,7 +364,7 @@ ExportMap.parse = function (path, content, context) {
362364
function resolveImport(value) {
363365
const rp = remotePath(value)
364366
if (rp == null) return null
365-
return ExportMap.for(childContext(rp, context))
367+
return ExportMap.for(childContext(rp, context), options)
366368
}
367369

368370
function getNamespace(identifier) {
@@ -390,7 +392,7 @@ ExportMap.parse = function (path, content, context) {
390392
const existing = m.imports.get(p)
391393
if (existing != null) return existing.getter
392394

393-
const getter = () => ExportMap.for(childContext(p, context))
395+
const getter = () => ExportMap.for(childContext(p, context), options)
394396
m.imports.set(p, {
395397
getter,
396398
source: { // capturing actual node reference holds full AST in memory!
@@ -401,8 +403,118 @@ ExportMap.parse = function (path, content, context) {
401403
return getter
402404
}
403405

406+
// for saving all commonjs exports
407+
let moduleExports = {}
408+
409+
// for if module exports has been declared directly (exports/module.exports = ...)
410+
let moduleExportsMain = null
411+
412+
function parseModuleExportsObjectExpression(node) {
413+
moduleExportsMain = true
414+
moduleExports = {}
415+
node.properties.forEach(
416+
function(property) {
417+
const keyType = property.key.type
418+
419+
if (keyType === 'Identifier') {
420+
const keyName = property.key.name
421+
moduleExports[keyName] = property.value
422+
}
423+
else if (keyType === 'Literal') {
424+
const keyName = property.key.value
425+
moduleExports[keyName] = property.value
426+
}
427+
}
428+
)
429+
}
430+
431+
function handleModuleExports() {
432+
let isEsModule = false
433+
const esModule = moduleExports.__esModule
434+
if (esModule && esModule.type === 'Literal' && esModule.value) {
435+
// for interopRequireDefault calls
436+
}
437+
438+
Object.getOwnPropertyNames(moduleExports).forEach(function (propertyName) {
439+
m.namespace.set(propertyName)
440+
})
441+
442+
if (!isEsModule && moduleExportsMain && !options.noInterop) {
443+
// recognizes default for import statements
444+
m.namespace.set('default')
445+
}
446+
}
404447

405448
ast.body.forEach(function (n) {
449+
if (options.useCommonjsExports) {
450+
if (n.type === 'ExpressionStatement') {
451+
if (n.expression.type === 'AssignmentExpression') {
452+
const left = n.expression.left
453+
const right = n.expression.right
454+
455+
// exports/module.exports = ...
456+
if (isCommonjsExportsObject(left)) {
457+
moduleExportsMain = true
458+
459+
// exports/module.exports = {...}
460+
if (right.type === 'ObjectExpression') {
461+
parseModuleExportsObjectExpression(right)
462+
}
463+
}
464+
else if (left.type === 'MemberExpression'
465+
&& isCommonjsExportsObject(left.object)) {
466+
// (exports/module.exports).<name> = ...
467+
if (left.property.type === 'Identifier') {
468+
const keyName = left.property.name
469+
moduleExports[keyName] = right
470+
}
471+
// (exports/module.exports).["<name>"] = ...
472+
else if (left.property.type === 'Literal') {
473+
const keyName = left.property.value
474+
moduleExports[keyName] = right
475+
}
476+
}
477+
else return
478+
}
479+
// Object.defineProperty((exports/module.exports), <name>, {value: <value>})
480+
else if (n.expression.type === 'CallExpression') {
481+
const call = n.expression
482+
483+
const callee = call.callee
484+
if (callee.type !== 'MemberExpression') return
485+
if (callee.object.type !== 'Identifier' || call.object.type !== 'Object') return
486+
if (callee.property.type !== 'Identifier' || call.property.name !== 'defineProperty') return
487+
488+
if (call.arguments.length !== 3) return
489+
if (!isCommonjsExportsObject(call.arguments[0])) return
490+
if (call.arguments[1].type !== 'Literal') return
491+
if (call.arguments[2].type !== 'ObjectExpression') return
492+
493+
call.arguments[2].properties.forEach(function (defineProperty) {
494+
if (defineProperty.type !== 'Property') return
495+
496+
if (defineProperty.key.type === 'Literal'
497+
&& defineProperty.key.value === 'value') {
498+
// {'value': <value>}
499+
Object.defineProperty(
500+
moduleExports,
501+
call.arguments[1].value,
502+
defineProperty.value
503+
)
504+
}
505+
else if (defineProperty.key.type === 'Identifier'
506+
&& defineProperty.key.name === 'value') {
507+
// {value: <value>}
508+
Object.defineProperty(
509+
moduleExports,
510+
call.arguments[1].value,
511+
defineProperty.value
512+
)
513+
}
514+
})
515+
}
516+
}
517+
}
406518

407519
if (n.type === 'ExportDefaultDeclaration') {
408520
const exportMeta = captureDoc(docStyleParsers, n)
@@ -483,6 +595,8 @@ ExportMap.parse = function (path, content, context) {
483595
}
484596
})
485597

598+
if (options.useCommonjsExports) handleModuleExports()
599+
486600
return m
487601
}
488602

@@ -527,3 +641,25 @@ function childContext(path, context) {
527641
path,
528642
}
529643
}
644+
645+
/**
646+
* Check if a given node is exports, module.exports, or module['exports']
647+
* @param {node} node
648+
* @return {boolean}
649+
*/
650+
function isCommonjsExportsObject(node) {
651+
// exports
652+
if (node.type === 'Identifier' && node.name === 'exports') return true
653+
654+
if (node.type !== 'MemberExpression') return false
655+
656+
if (node.object.type === 'Identifier' && node.object.name === 'module') {
657+
// module.exports
658+
if (node.property.type === 'Identifier' && node.property.name === 'exports') return true
659+
660+
// module['exports']
661+
if (node.property.type === 'Literal' && node.property.value === 'exports') return true
662+
}
663+
664+
return false
665+
}

src/rules/named.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,31 @@ module.exports = {
77
docs: {
88
url: docsUrl('named'),
99
},
10+
schema : [{
11+
type: 'object',
12+
properties: {
13+
commonjs: {
14+
oneOf: [
15+
{ type: 'boolean' },
16+
{
17+
type: 'object',
18+
properties: {
19+
require: { type: 'boolean' },
20+
exports: { type: 'boolean' },
21+
},
22+
},
23+
],
24+
},
25+
},
26+
additionalProperties: false,
27+
}],
1028
},
1129

1230
create: function (context) {
31+
const options = context.options[0] || {}
32+
const { commonjs = {} } = options
33+
const useCommonjsExports = typeof commonjs === 'boolean' ? commonjs : commonjs.exports
34+
1335
function checkSpecifiers(key, type, node) {
1436
// ignore local exports and type imports
1537
if (node.source == null || node.importKind === 'type') return
@@ -19,7 +41,12 @@ module.exports = {
1941
return // no named imports/exports
2042
}
2143

22-
const imports = Exports.get(node.source.value, context)
44+
const exportsOptions = {
45+
useCommonjsExports,
46+
noInterop: false, // this should only be true when using require() calls
47+
}
48+
49+
const imports = Exports.get(node.source.value, context, exportsOptions)
2350
if (imports == null) return
2451

2552
if (imports.errors.length) {

0 commit comments

Comments
 (0)