Skip to content

Commit

Permalink
add module exports to named exports
Browse files Browse the repository at this point in the history
  • Loading branch information
vikr01 committed Nov 2, 2018
1 parent db471a8 commit 81dade6
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 10 deletions.
154 changes: 145 additions & 9 deletions src/ExportMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,14 @@ function captureTomDoc(comments) {
}
}

ExportMap.get = function (source, context) {
ExportMap.get = function (source, context, options) {
const path = resolve(source, context)
if (path == null) return null

return ExportMap.for(childContext(path, context))
return ExportMap.for(childContext(path, context), options)
}

ExportMap.for = function (context) {
ExportMap.for = function (context, options = {}) {
const { path } = context

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

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

log('cache miss', cacheKey, 'for path', path)
exportMap = ExportMap.parse(path, content, context)
exportMap = ExportMap.parse(path, content, context, options)

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


ExportMap.parse = function (path, content, context) {
ExportMap.parse = function (path, content, context, options = {}) {
log('using commonjs exports:', options.useCommonjsExports)

var m = new ExportMap(path)

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

if (!unambiguous.isModule(ast)) return null
if (!options.useCommonjsExports && !unambiguous.isModule(ast)) return null

const docstyle = (context.settings && context.settings['import/docstyle']) || ['jsdoc']
const docStyleParsers = {}
Expand Down Expand Up @@ -362,7 +364,7 @@ ExportMap.parse = function (path, content, context) {
function resolveImport(value) {
const rp = remotePath(value)
if (rp == null) return null
return ExportMap.for(childContext(rp, context))
return ExportMap.for(childContext(rp, context), options)
}

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

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

// for saving all commonjs exports
let moduleExports = {}

// for if module exports has been declared directly (exports/module.exports = ...)
let moduleExportsMain = null

function parseModuleExportsObjectExpression(node) {
moduleExportsMain = true
moduleExports = {}
node.properties.forEach(
function(property) {
const keyType = property.key.type

if (keyType === 'Identifier') {
const keyName = property.key.name
moduleExports[keyName] = property.value
}
else if (keyType === 'Literal') {
const keyName = property.key.value
moduleExports[keyName] = property.value
}
}
)
}

function handleModuleExports() {
let isEsModule = false
const esModule = moduleExports.__esModule
if (esModule && esModule.type === 'Literal' && esModule.value) {
// for interopRequireDefault calls
}

Object.getOwnPropertyNames(moduleExports).forEach(function (propertyName) {
m.namespace.set(propertyName)
})

if (!isEsModule && moduleExportsMain && !options.noInterop) {
// recognizes default for import statements
m.namespace.set('default')
}
}

ast.body.forEach(function (n) {
if (options.useCommonjsExports) {
if (n.type === 'ExpressionStatement') {
if (n.expression.type === 'AssignmentExpression') {
const left = n.expression.left
const right = n.expression.right

// exports/module.exports = ...
if (isCommonjsExportsObject(left)) {
moduleExportsMain = true

// exports/module.exports = {...}
if (right.type === 'ObjectExpression') {
parseModuleExportsObjectExpression(right)
}
}
else if (left.type === 'MemberExpression'
&& isCommonjsExportsObject(left.object)) {
// (exports/module.exports).<name> = ...
if (left.property.type === 'Identifier') {
const keyName = left.property.name
moduleExports[keyName] = right
}
// (exports/module.exports).["<name>"] = ...
else if (left.property.type === 'Literal') {
const keyName = left.property.value
moduleExports[keyName] = right
}
}
else return
}
// Object.defineProperty((exports/module.exports), <name>, {value: <value>})
else if (n.expression.type === 'CallExpression') {
const call = n.expression

const callee = call.callee
if (callee.type !== 'MemberExpression') return
if (callee.object.type !== 'Identifier' || call.object.type !== 'Object') return
if (callee.property.type !== 'Identifier' || call.property.name !== 'defineProperty') return

if (call.arguments.length !== 3) return
if (!isCommonjsExportsObject(call.arguments[0])) return
if (call.arguments[1].type !== 'Literal') return
if (call.arguments[2].type !== 'ObjectExpression') return

call.arguments[2].properties.forEach(function (defineProperty) {
if (defineProperty.type !== 'Property') return

if (defineProperty.key.type === 'Literal'
&& defineProperty.key.value === 'value') {
// {'value': <value>}
Object.defineProperty(
moduleExports,
call.arguments[1].value,
defineProperty.value
)
}
else if (defineProperty.key.type === 'Identifier'
&& defineProperty.key.name === 'value') {
// {value: <value>}
Object.defineProperty(
moduleExports,
call.arguments[1].value,
defineProperty.value
)
}
})
}
}
}

if (n.type === 'ExportDefaultDeclaration') {
const exportMeta = captureDoc(docStyleParsers, n)
Expand Down Expand Up @@ -483,6 +595,8 @@ ExportMap.parse = function (path, content, context) {
}
})

if (options.useCommonjsExports) handleModuleExports()

return m
}

Expand Down Expand Up @@ -527,3 +641,25 @@ function childContext(path, context) {
path,
}
}

/**
* Check if a given node is exports, module.exports, or module['exports']
* @param {node} node
* @return {boolean}
*/
function isCommonjsExportsObject(node) {
// exports
if (node.type === 'Identifier' && node.name === 'exports') return true

if (node.type !== 'MemberExpression') return false

if (node.object.type === 'Identifier' && node.object.name === 'module') {
// module.exports
if (node.property.type === 'Identifier' && node.property.name === 'exports') return true

// module['exports']
if (node.property.type === 'Literal' && node.property.value === 'exports') return true
}

return false
}
29 changes: 28 additions & 1 deletion src/rules/named.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,31 @@ module.exports = {
docs: {
url: docsUrl('named'),
},
schema : [{
type: 'object',
properties: {
commonjs: {
oneOf: [
{ type: 'boolean' },
{
type: 'object',
properties: {
require: { type: 'boolean' },
exports: { type: 'boolean' },
},
},
],
},
},
additionalProperties: false,
}],
},

create: function (context) {
const options = context.options[0] || {}
const { commonjs = {} } = options
const useCommonjsExports = typeof commonjs === 'boolean' ? commonjs : commonjs.exports

function checkSpecifiers(key, type, node) {
// ignore local exports and type imports
if (node.source == null || node.importKind === 'type') return
Expand All @@ -19,7 +41,12 @@ module.exports = {
return // no named imports/exports
}

const imports = Exports.get(node.source.value, context)
const exportsOptions = {
useCommonjsExports,
noInterop: false, // this should only be true when using require() calls
}

const imports = Exports.get(node.source.value, context, exportsOptions)
if (imports == null) return

if (imports.errors.length) {
Expand Down

0 comments on commit 81dade6

Please sign in to comment.