canonical
Version:
Canonical code style linter and formatter for JavaScript, SCSS and CSS.
115 lines (94 loc) • 3.38 kB
JavaScript
import Exports from '../core/getExports'
import importDeclaration from '../importDeclaration'
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) {
context.report({
node: declaration.source,
message: `Parse errors in imported module ` +
`'${declaration.source.value}'.`,
})
return
}
if (!imports.hasNamed) {
context.report(namespace,
`No exported names found in module '${declaration.source.value}'.`)
}
return imports
}
function message(identifier, namespace) {
return '\'' + identifier.name +
'\' not found in imported namespace ' +
namespace.name + '.'
}
function declaredScope(name) {
let references = context.getScope().references
, i
for (i = 0; i < references.length; i++) {
if (references[i].identifier.name === name) {
break
}
}
if (!references[i]) return undefined
return references[i].resolved.scope.type
}
return {
'ImportNamespaceSpecifier': function (namespace) {
const imports = getImportsAndReport(namespace)
if (imports == null) return
namespaces.set(namespace.local.name, imports.named)
},
// same as above, but does not add names to local map
'ExportNamespaceSpecifier': function (namespace) {
getImportsAndReport(namespace)
},
// todo: check for possible redefinition
'MemberExpression': function (dereference) {
if (dereference.object.type !== 'Identifier') return
if (!namespaces.has(dereference.object.name)) return
if (dereference.parent.type === 'AssignmentExpression' &&
dereference.parent.left === dereference) {
context.report(dereference.parent,
`Assignment to member of namespace '${dereference.object.name}'.`)
}
if (dereference.computed) {
context.report(dereference.property,
'Unable to validate computed reference to imported namespace \'' +
dereference.object.name + '\'.')
return
}
var namespace = namespaces.get(dereference.object.name)
if (!namespace.has(dereference.property.name)) {
context.report( dereference.property
, message(dereference.property, dereference.object)
)
}
},
'VariableDeclarator': function ({ id, init }) {
if (init == null) return
if (id.type !== 'ObjectPattern') return
if (init.type !== 'Identifier') return
if (!namespaces.has(init.name)) return
// check for redefinition in intermediate scopes
if (declaredScope(init.name) !== 'module') return
const namespace = namespaces.get(init.name)
for (let property of id.properties) {
if (property.key.type !== 'Identifier') {
context.report({
node: property,
message: 'Only destructure top-level names.',
})
} else if (!namespace.has(property.key.name)) {
context.report({
node: property,
message: message(property.key, init),
})
}
}
},
}
}