Skip to content

Commit

Permalink
wip: no-cycle support with general dependency "imports" map in ExportMap
Browse files Browse the repository at this point in the history
  • Loading branch information
benmosher committed Mar 19, 2018
1 parent 220f209 commit 5fa2851
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 8 deletions.
38 changes: 30 additions & 8 deletions src/ExportMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,16 @@ export default class ExportMap {
this.namespace = new Map()
// todo: restructure to key on path, value is resolver + map of names
this.reexports = new Map()
this.dependencies = new Map()
/**
* star-exports
* @type {Set} of () => ExportMap
*/
this.dependencies = new Set()
/**
* dependencies of this module that are not explicitly re-exported
* @type {Map} from path = () => ExportMap
*/
this.imports = new Map()
this.errors = []
}

Expand All @@ -46,7 +55,7 @@ export default class ExportMap {

// default exports must be explicitly re-exported (#328)
if (name !== 'default') {
for (let dep of this.dependencies.values()) {
for (let dep of this.dependencies) {
let innerMap = dep()

// todo: report as unresolved?
Expand Down Expand Up @@ -88,7 +97,7 @@ export default class ExportMap {

// default exports must be explicitly re-exported (#328)
if (name !== 'default') {
for (let dep of this.dependencies.values()) {
for (let dep of this.dependencies) {
let innerMap = dep()
// todo: report as unresolved?
if (!innerMap) continue
Expand Down Expand Up @@ -125,7 +134,7 @@ export default class ExportMap {

// default exports must be explicitly re-exported (#328)
if (name !== 'default') {
for (let dep of this.dependencies.values()) {
for (let dep of this.dependencies) {
let innerMap = dep()
// todo: report as unresolved?
if (!innerMap) continue
Expand Down Expand Up @@ -373,6 +382,18 @@ ExportMap.parse = function (path, content, context) {
return object
}

function captureDependency(declaration) {
if (declaration.source == null) return

const p = remotePath(declaration)
if (p == null) return
if (m.imports.has(p)) return

const getter = () => ExportMap.for(p, context)
m.imports.set(p, getter)
return getter
}


ast.body.forEach(function (n) {

Expand All @@ -386,22 +407,23 @@ ExportMap.parse = function (path, content, context) {
}

if (n.type === 'ExportAllDeclaration') {
let remoteMap = remotePath(n)
if (remoteMap == null) return
m.dependencies.set(remoteMap, () => ExportMap.for(remoteMap, context))
const getter = captureDependency(n)
if (getter) m.dependencies.add(getter)
return
}

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

if (n.type === 'ExportNamedDeclaration'){
if (n.type === 'ExportNamedDeclaration') {
captureDependency(n)
// capture declaration
if (n.declaration != null) {
switch (n.declaration.type) {
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const rules = {
'group-exports': require('./rules/group-exports'),

'no-self-import': require('./rules/no-self-import'),
'no-cycle': require('./rules/no-cycle'),
'no-named-default': require('./rules/no-named-default'),
'no-named-as-default': require('./rules/no-named-as-default'),
'no-named-as-default-member': require('./rules/no-named-as-default-member'),
Expand Down
62 changes: 62 additions & 0 deletions src/rules/no-cycle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* @fileOverview Ensures that no imported module imports the linted module.
* @author Ben Mosher
*/

import Exports from '../ExportMap'
import moduleVisitor, { optionsSchema } from 'eslint-module-utils/moduleVisitor'
import docsUrl from '../docsUrl'

// todo: cache cycles / deep relationships for faster repeat evaluation
module.exports = {
meta: {
docs: {
url: docsUrl('no-cycle'),
},

schema: [optionsSchema],
},

create: function (context) {

const myPath = context.getFilename()

function checkSourceValue(source) {
const imported = Exports.get(source.value, context)

if (imported === undefined) {
return // no-unresolved territory
}

if (imported.path === myPath) {
// todo: report direct self import?
return
}

const untraversed = [imported]
const traversed = new Set()
function detectCycle(m) {
if (traversed.has(m.path)) return
traversed.add(m.path)

for (let [path, getter] of m.imports) {
if (path === context) return true
if (traversed.has(path)) continue
untraversed.push(getter())
}
}

while (untraversed.length > 0) {
if (detectCycle(untraversed.pop())) {
// todo: report

// todo: cache

return
}
}
}

return moduleVisitor(checkSourceValue, context.options[0])
},
}

0 comments on commit 5fa2851

Please sign in to comment.