UNPKG

canonical

Version:

Canonical code style linter and formatter for JavaScript, SCSS and CSS.

189 lines (149 loc) 4.69 kB
import * as fs from 'fs' import { createHash } from 'crypto' import parse from './parse' import resolve from './resolve' import isIgnored from './ignore' // map from settings sha1 => path => export map objects const exportCaches = new Map() export default class ExportMap { constructor(context) { this.context = context this.named = new Set() this.errors = [] } get settings() { return this.context && this.context.settings } get hasDefault() { return this.named.has('default') } get hasNamed() { return this.named.size > (this.hasDefault ? 1 : 0) } static get(source, context) { var path = resolve(source, context) if (path == null) return null return ExportMap.for(path, context) } static for(path, context) { let exportMap const cacheKey = hashObject(context.settings) let exportCache = exportCaches.get(cacheKey) if (exportCache === undefined) { exportCache = new Map() exportCaches.set(cacheKey, exportCache) } exportMap = exportCache.get(path) // return cached ignore if (exportMap === null) return null const stats = fs.statSync(path) if (exportMap != null) { // date equality check if (exportMap.mtime - stats.mtime === 0) { return exportMap } // future: check content equality? } exportMap = ExportMap.parse(path, context) exportMap.mtime = stats.mtime // ignore empties, optionally if (exportMap.named.size === 0 && isIgnored(path, context)) { exportMap = null } exportCache.set(path, exportMap) return exportMap } static parse(path, context) { var m = new ExportMap(context) try { var ast = parse(path, context) } catch (err) { m.errors.push(err) return m // can't continue } ast.body.forEach(function (n) { m.captureDefault(n) m.captureAll(n, path) m.captureNamedDeclaration(n, path) }) return m } resolveReExport(node, base) { var remotePath = resolve.relative(node.source.value, base, this.settings) if (remotePath == null) return null return ExportMap.for(remotePath, this.context) } captureDefault(n) { if (n.type !== 'ExportDefaultDeclaration') return this.named.add('default') } /** * capture all named exports from remote module. * * returns null if this node wasn't an ExportAllDeclaration * returns false if it was not resolved * returns true if it was resolved + parsed * * @param {node} n * @param {string} path - the path of the module currently parsing * @return {boolean?} */ captureAll(n, path) { if (n.type !== 'ExportAllDeclaration') return null var remoteMap = this.resolveReExport(n, path) if (remoteMap == null) return false remoteMap.named.forEach(function (name) { this.named.add(name) }.bind(this)) return true } captureNamedDeclaration(n, path) { if (n.type !== 'ExportNamedDeclaration') return // capture declaration if (n.declaration != null) { switch (n.declaration.type) { case 'FunctionDeclaration': case 'ClassDeclaration': case 'TypeAlias': // flowtype with babel-eslint parser this.named.add(n.declaration.id.name) break case 'VariableDeclaration': n.declaration.declarations.forEach((d) => recursivePatternCapture(d.id, id => this.named.add(id.name))) break } } // capture specifiers let remoteMap if (n.source) remoteMap = this.resolveReExport(n, path) n.specifiers.forEach(function (s) { if (s.type === 'ExportDefaultSpecifier') { // don't add it if it is not present in the exported module if (!remoteMap || !remoteMap.hasDefault) return } this.named.add(s.exported.name) }.bind(this)) } } /** * Traverse a patter/identifier node, calling 'callback' * for each leaf identifier. * @param {node} pattern * @param {Function} callback * @return {void} */ export function recursivePatternCapture(pattern, callback) { switch (pattern.type) { case 'Identifier': // base case callback(pattern) break case 'ObjectPattern': pattern.properties.forEach(({ value }) => { recursivePatternCapture(value, callback) }) break case 'ArrayPattern': pattern.elements.forEach((element) => { if (element == null) return recursivePatternCapture(element, callback) }) break } } function hashObject(object) { const settingsShasum = createHash('sha1') settingsShasum.update(JSON.stringify(object)) return settingsShasum.digest('hex') }