Skip to content

Commit

Permalink
deep namespace: supports namespaces imported as normal names or defau…
Browse files Browse the repository at this point in the history
…lts (fixes #189)
  • Loading branch information
benmosher committed Feb 25, 2016
1 parent 6dc9bf8 commit c69386b
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 29 deletions.
18 changes: 14 additions & 4 deletions src/core/getExports.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,21 @@ export default class ExportMap {

const namespaces = new Map()

function getNamespace(identifier) {
if (!namespaces.has(identifier.name)) return

let namespace = m.resolveReExport(namespaces.get(identifier.name), path)
if (namespace) return { namespace: namespace.named }
}

ast.body.forEach(function (n) {

if (n.type === 'ExportDefaultDeclaration') {
m.named.set('default', captureDoc(n))
const exportMeta = captureDoc(n)
if (n.declaration.type === 'Identifier') {
Object.assign(exportMeta, getNamespace(n.declaration))
}
m.named.set('default', exportMeta)
return
}

Expand Down Expand Up @@ -146,9 +157,8 @@ export default class ExportMap {
if (s.type === 'ExportDefaultSpecifier') {
// don't add it if it is not present in the exported module
if (!remoteMap || !remoteMap.hasDefault) return
} else if (s.type === 'ExportSpecifier' && namespaces.has(s.local.name)){
let namespace = m.resolveReExport(namespaces.get(s.local.name), path)
if (namespace) exportMeta.namespace = namespace.named
} else if (s.type === 'ExportSpecifier'){
Object.assign(exportMeta, getNamespace(s.local))
} else if (s.type === 'ExportNamespaceSpecifier') {
exportMeta.namespace = remoteMap.named
}
Expand Down
68 changes: 44 additions & 24 deletions src/rules/namespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,61 @@ module.exports = function (context) {

const namespaces = new Map()

function getImportsAndReport(namespace) {
var declaration = importDeclaration(context)

var imports = Exports.get(declaration.source.value, context)
if (imports == null) return null

if (imports.errors.length) {
imports.reportErrors(context, declaration)
return
}

if (!imports.hasNamed) {
context.report(namespace,
`No exported names found in module '${declaration.source.value}'.`)
}

return imports
}

function makeMessage(last, namepath) {
return `'${last.name}' not found in` +
(namepath.length > 1 ? ' deeply ' : ' ') +
`imported namespace '${namepath.join('.')}'.`
}

return {
'ImportNamespaceSpecifier': function (namespace) {
const imports = getImportsAndReport(namespace)
if (imports == null) return
namespaces.set(namespace.local.name, imports.named)

'ImportDeclaration': function (declaration) {
const imports = Exports.get(declaration.source.value, context)
if (imports == null) return null

if (imports.errors.length) {
imports.reportErrors(context, declaration)
return
}

for (let specifier of declaration.specifiers) {
switch (specifier.type) {
case 'ImportNamespaceSpecifier':
if (!imports.hasNamed) {
context.report(specifier,
`No exported names found in module '${declaration.source.value}'.`)
}
namespaces.set(specifier.local.name, imports.named)
break
case 'ImportDefaultSpecifier':
case 'ImportSpecifier': {
const meta = imports.named.get(
// default to 'default' for default http://i.imgur.com/nj6qAWy.jpg
specifier.imported ? specifier.imported.name : 'default')
if (!meta || !meta.namespace) break
namespaces.set(specifier.local.name, meta.namespace)
break
}
}
}
},

// same as above, but does not add names to local map
'ExportNamespaceSpecifier': function (namespace) {
getImportsAndReport(namespace)
var declaration = importDeclaration(context)

var imports = Exports.get(declaration.source.value, context)
if (imports == null) return null

if (imports.errors.length) {
imports.reportErrors(context, declaration)
return
}

if (!imports.hasNamed) {
context.report(namespace,
`No exported names found in module '${declaration.source.value}'.`)
}
},

// todo: check for possible redefinition
Expand Down
2 changes: 2 additions & 0 deletions tests/files/deep/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import * as b from './b'
export default b
7 changes: 7 additions & 0 deletions tests/src/core/getExports.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ describe('getExports', function () {
expect(a.named.get('b').namespace.has('c')).to.be.true
})

it('captures namespace exported as default', function () {
const def = ExportMap.parse(getFilename('deep/default.js'), espreeContext)
expect(def.errors).to.be.empty
expect(def.named.get('default').namespace).to.exist
expect(def.named.get('default').namespace.has('c')).to.be.true
})

it('works with babel-eslint & ES7 namespace exports', function () {
const a = ExportMap.parse(getFilename('deep-es7/a.js'), babelContext)
expect(a.errors).to.be.empty
Expand Down
18 changes: 17 additions & 1 deletion tests/src/rules/namespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ const invalid = [
}],
}),

test({
code: "import b from './deep/default'; console.log(b.e)",
errors: [ "'e' not found in imported namespace 'b'." ],
}),
]

///////////////////////
Expand All @@ -128,20 +132,32 @@ const invalid = [
;[['deep', 'espree'], ['deep-es7', 'babel-eslint']].forEach(function ([folder, parser]) { // close over params
valid.push(
test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.c.d.e)` }),
test({ parser, code: `import { b } from "./${folder}/a"; console.log(b.c.d.e)` }),
test({ parser, code: `import * as a from "./${folder}/a"; console.log(a.b.c.d.e.f)` }),
test({ parser, code: `import * as a from "./${folder}/a"; var {b:{c:{d:{e}}}} = a` }))
test({ parser, code: `import * as a from "./${folder}/a"; var {b:{c:{d:{e}}}} = a` }),
test({ parser, code: `import { b } from "./${folder}/a"; var {c:{d:{e}}} = b` }))

invalid.push(
test({
parser,
code: `import * as a from "./${folder}/a"; console.log(a.b.e)`,
errors: [ "'e' not found in deeply imported namespace 'a.b'." ],
}),
test({
parser,
code: `import { b } from "./${folder}/a"; console.log(b.e)`,
errors: [ "'e' not found in imported namespace 'b'." ],
}),
test({
parser,
code: `import * as a from "./${folder}/a"; console.log(a.b.c.e)`,
errors: [ "'e' not found in deeply imported namespace 'a.b.c'." ],
}),
test({
parser,
code: `import { b } from "./${folder}/a"; console.log(b.c.e)`,
errors: [ "'e' not found in deeply imported namespace 'b.c'." ],
}),
test({
parser,
code: `import * as a from "./${folder}/a"; var {b:{ e }} = a`,
Expand Down

0 comments on commit c69386b

Please sign in to comment.