Skip to content

Commit

Permalink
first steps toward #119: collecting deep namespaces, and failing tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Mosher committed Jan 17, 2016
1 parent ff0ef21 commit 5bbcbaa
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 12 deletions.
35 changes: 24 additions & 11 deletions src/core/getExports.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const exportCaches = new Map()
export default class ExportMap {
constructor(context) {
this.context = context
this.named = new Set()
this.named = new Map()

this.errors = []
}
Expand Down Expand Up @@ -76,10 +76,12 @@ export default class ExportMap {
return m // can't continue
}

const namespaces = new Map()

ast.body.forEach(function (n) {
m.captureDefault(n)
m.captureAll(n, path)
m.captureNamedDeclaration(n, path)
m.captureNamedDeclaration(n, path, namespaces)
})

return m
Expand All @@ -94,7 +96,7 @@ export default class ExportMap {

captureDefault(n) {
if (n.type !== 'ExportDefaultDeclaration') return
this.named.add('default')
this.named.set('default', null)
}

/**
Expand All @@ -114,12 +116,19 @@ export default class ExportMap {
var remoteMap = this.resolveReExport(n, path)
if (remoteMap == null) return false

remoteMap.named.forEach(function (name) { this.named.add(name) }.bind(this))
remoteMap.named.forEach((val, name) => { this.named.set(name, val) })

return true
}

captureNamedDeclaration(n, path) {
captureNamedDeclaration(n, path, namespaces) {
// capture namespaces in case of later export
if (n.type === 'ImportDeclaration') {
let ns
if (n.specifiers.some(s => s.type === 'ImportNamespaceSpecifier' && (ns = s))) {
namespaces.set(ns.local, ns)
}
}
if (n.type !== 'ExportNamedDeclaration') return

// capture declaration
Expand All @@ -128,11 +137,11 @@ export default class ExportMap {
case 'FunctionDeclaration':
case 'ClassDeclaration':
case 'TypeAlias': // flowtype with babel-eslint parser
this.named.add(n.declaration.id.name)
this.named.set(n.declaration.id.name, null) // todo: capture type info
break
case 'VariableDeclaration':
n.declaration.declarations.forEach((d) =>
recursivePatternCapture(d.id, id => this.named.add(id.name)))
recursivePatternCapture(d.id, id => this.named.set(id.name, null)))
break
}
}
Expand All @@ -141,20 +150,24 @@ export default class ExportMap {
let remoteMap
if (n.source) remoteMap = this.resolveReExport(n, path)

n.specifiers.forEach(function (s) {
n.specifiers.forEach((s) => {
let type = null
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)){
let namespace = this.resolveReExport(namespaces.get(s.local), path)
if (namespace) type = namespace.named
}

this.named.add(s.exported.name)
}.bind(this))
this.named.set(s.exported.name, type)
})
}
}


/**
* Traverse a patter/identifier node, calling 'callback'
* Traverse a pattern/identifier node, calling 'callback'
* for each leaf identifier.
* @param {node} pattern
* @param {Function} callback
Expand Down
2 changes: 1 addition & 1 deletion src/rules/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ module.exports = function (context) {
`No named exports found in module '${node.source.value}'.`)
}

for (let name of remoteExports.named) {
for (let [name] of remoteExports.named) {
addNamed(name, node)
}
},
Expand Down
5 changes: 5 additions & 0 deletions src/rules/namespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ module.exports = function (context) {
context.report( dereference.property
, message(dereference.property, dereference.object)
)
return
}

// go deep
// todo: while property is namespace and parent is member expression, keep validating
// if (!dereference.parent.type === 'MemberExpression') return
},

'VariableDeclarator': function ({ id, init }) {
Expand Down
2 changes: 2 additions & 0 deletions tests/files/deep/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import * as b from './b'
export { b }
2 changes: 2 additions & 0 deletions tests/files/deep/b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import * as c from './c'
export { c }
2 changes: 2 additions & 0 deletions tests/files/deep/c.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import * as d from './d'
export { d }
1 change: 1 addition & 0 deletions tests/files/deep/d.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const e = "e"
19 changes: 19 additions & 0 deletions tests/src/rules/namespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ ruleTester.run('namespace', rule, {
// non-existent is handled by no-unresolved
test({ code: 'export * as names from "./does-not-exist"'
, parser: 'babel-eslint' }),

///////////////////////
// deep dereferences //
///////////////////////

test({ code: 'import * as a from "./deep/a"; console.log(a.b.c.d.e)' }),
],

invalid: [
Expand Down Expand Up @@ -112,5 +118,18 @@ ruleTester.run('namespace', rule, {
type: 'Literal',
}],
}),


///////////////////////
// deep dereferences //
///////////////////////
test({
code: 'import * as a from "./deep/a"; console.log(a.b.e)',
errors: [ "'e' not found in deeply imported namespace b from ./deep/b.js." ],
}),
test({
code: 'import * as a from "./deep/a"; console.log(a.b.c.e)',
errors: [ "'e' not found in deeply imported namespace c from ./deep/c.js." ],
}),
],
})

0 comments on commit 5bbcbaa

Please sign in to comment.