Skip to content

Commit

Permalink
Chore: refactoring for no-unsupported-features/*
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea committed Jul 17, 2018
1 parent 8f25248 commit fa25150
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 150 deletions.
8 changes: 6 additions & 2 deletions lib/rules/no-unsupported-features/es-builtins.js
Expand Up @@ -5,7 +5,7 @@
"use strict"

const { READ } = require("eslint-utils")
const defineUnsupportedModuleHandlers = require("../../util/define-unsupported-module-handlers")
const checkUnsupportedBuiltins = require("../../util/check-unsupported-builtins")
const enumeratePropertyNames = require("../../util/enumerate-property-names")

const trackMap = {
Expand Down Expand Up @@ -157,6 +157,10 @@ module.exports = {
},
},
create(context) {
return defineUnsupportedModuleHandlers(context, trackMap)
return {
"Program:exit"() {
checkUnsupportedBuiltins(context, trackMap)
},
}
},
}
100 changes: 46 additions & 54 deletions lib/rules/no-unsupported-features/es-syntax.js
Expand Up @@ -6,8 +6,9 @@

const { rules: esRules } = require("eslint-plugin-es")
const { getInnermostScope } = require("eslint-utils")
const semver = require("semver")
const getEnginesNode = require("../../util/get-engines-node")
const { Range } = require("semver") //eslint-disable-line no-unused-vars
const getConfiguredNodeVersion = require("../../util/get-configured-node-version")
const getSemverRange = require("../../util/get-semver-range")

const getOrSet = /^(?:g|s)et$/
const features = {
Expand Down Expand Up @@ -353,42 +354,18 @@ const keywords = Object.keys(features)

/**
* Parses the options.
* @param {object|undefined} options - An option object to parse.
* @param {string} defaultVersion - The default version to use if the version option was omitted.
* @returns {{version:string,ignores:Set<string>}} Parsed value.
* @param {RuleContext} context The rule context.
* @returns {{version:Range,ignores:Set<string>}} Parsed value.
*/
function parseOptions(options, defaultVersion) {
const version =
semver.validRange(options && options.version) || defaultVersion
const ignores = new Set((options && options.ignores) || [])
function parseOptions(context) {
const raw = context.options[0] || {}
const filePath = context.getFilename()
const version = getConfiguredNodeVersion(raw.version, filePath)
const ignores = new Set(raw.ignores || [])

return Object.freeze({ version, ignores })
}

/**
* Define the case selector with given information.
* @param {RuleContext} context The rule context to get scopes.
* @param {string} version The configured version range.
* @param {Node|null} node The node at the current location, for additional conditions.
* @returns {function(aCase:object):boolean} The case selector.
*/
function defineSelector(context, version, node) {
return aCase =>
// Version.
(!aCase.supported ||
semver.intersects(`<${aCase.supported}`, version)) &&
// Additional conditions.
(!aCase.test ||
aCase.test({
node,
get isStrict() {
return Boolean(
node && nomalizeScope(context.getScope(), node).isStrict
)
},
}))
}

/**
* Find the scope that a given node belongs to.
* @param {Scope} initialScope The initial scope to find.
Expand Down Expand Up @@ -443,25 +420,47 @@ function dispatch(handlers, node) {
/**
* Define the visitor object as merging the rules of eslint-plugin-es.
* @param {RuleContext} context The rule context.
* @param {{version:string,ignores:Set<string>}} options The options.
* @param {{version:Range,ignores:Set<string>}} options The options.
* @returns {object} The defined visitor.
*/
function defineVisitor(context, options) {
const testInfoPrototype = {
get isStrict() {
return nomalizeScope(context.getScope(), this.node).isStrict
},
}

/**
* Check whether a given case object is full-supported on the configured node version.
* @param {{supported:string}} aCase The case object to check.
* @returns {boolean} `true` if it's supporting.
*/
function isNotSupportingVersion(aCase) {
return (
!aCase.supported ||
options.version.intersects(getSemverRange(`<${aCase.supported}`))
)
}

/**
* Define the predicate function to check whether a given case object is supported on the configured node version.
* @param {Node} node The node which is reported.
* @returns {function(aCase:{supported:string}):boolean} The predicate function.
*/
function isNotSupportingOn(node) {
return aCase =>
isNotSupportingVersion(aCase) &&
(!aCase.test || aCase.test({ node, __proto__: testInfoPrototype }))
}

return (
keywords
// Omit full-supported features and ignored features by options
// because this rule never reports those.
.filter(
keyword =>
!options.ignores.has(keyword) &&
features[keyword].cases.some(
c =>
!c.supported ||
semver.intersects(
options.version,
`<${c.supported}`
)
)
features[keyword].cases.some(isNotSupportingVersion)
)
// Merge remaining features with overriding `context.report()`.
.reduce((visitor, keyword) => {
Expand All @@ -476,20 +475,15 @@ function defineVisitor(context, options) {
report(descriptor) {
// Set additional information.
if (descriptor.data) {
descriptor.data.version = options.version
descriptor.data.version = options.version.raw
} else {
descriptor.data = { version: options.version }
descriptor.data = { version: options.version.raw }
}
descriptor.fix = undefined

// Test and report.
const hitCase = cases.find(
defineSelector(
this,
options.version,
descriptor.node
)
)
const node = descriptor.node
const hitCase = cases.find(isNotSupportingOn(node))
if (hitCase) {
descriptor.messageId = hitCase.messageId
descriptor.data.supported = hitCase.supported
Expand Down Expand Up @@ -628,8 +622,6 @@ module.exports = {
},
},
create(context) {
const defaultVersion = getEnginesNode(context.getFilename())
const options = parseOptions(context.options[0], defaultVersion)
return defineVisitor(context, options)
return defineVisitor(context, parseOptions(context))
},
}
8 changes: 6 additions & 2 deletions lib/rules/no-unsupported-features/node-builtins.js
Expand Up @@ -5,7 +5,7 @@
"use strict"

const { READ } = require("eslint-utils")
const defineUnsupportedModuleHandlers = require("../../util/define-unsupported-module-handlers")
const checkUnsupportedBuiltins = require("../../util/check-unsupported-builtins")
const enumeratePropertyNames = require("../../util/enumerate-property-names")

/*eslint-disable camelcase */
Expand Down Expand Up @@ -244,6 +244,10 @@ module.exports = {
},
},
create(context) {
return defineUnsupportedModuleHandlers(context, trackMap)
return {
"Program:exit"() {
checkUnsupportedBuiltins(context, trackMap)
},
}
},
}
61 changes: 61 additions & 0 deletions lib/util/check-unsupported-builtins.js
@@ -0,0 +1,61 @@
/**
* @author Toru Nagashima
* See LICENSE file in root directory for full license.
*/
"use strict"

const { Range } = require("semver") //eslint-disable-line no-unused-vars
const { ReferenceTracker } = require("eslint-utils")
const getConfiguredNodeVersion = require("./get-configured-node-version")
const getSemverRange = require("./get-semver-range")

/**
* Parses the options.
* @param {RuleContext} context The rule context.
* @returns {{version:Range,ignores:Set<string>}} Parsed value.
*/
function parseOptions(context) {
const raw = context.options[0] || {}
const filePath = context.getFilename()
const version = getConfiguredNodeVersion(raw.version, filePath)
const ignores = new Set(raw.ignores || [])

return Object.freeze({ version, ignores })
}

/**
* Verify the code to report unsupported APIs.
* @param {RuleContext} context The rule context.
* @param {{modules:object,globals:object}} trackMap The map for APIs to report.
* @returns {void}
*/
module.exports = function checkUnsupportedBuiltins(context, trackMap) {
const options = parseOptions(context)
const tracker = new ReferenceTracker(context.getScope(), {
mode: "legacy",
})
const references = [
...tracker.iterateCjsReferences(trackMap.modules || {}),
...tracker.iterateEsmReferences(trackMap.modules || {}),
...tracker.iterateGlobalReferences(trackMap.globals || {}),
]

for (const { node, path, info } of references) {
const name = path.join(".")
const supported = options.version.intersects(
getSemverRange(`<${info.supported}`)
)

if (supported && !options.ignores.has(name)) {
context.report({
node,
messageId: "unsupported",
data: {
name,
supported: info.supported,
version: options.version.raw,
},
})
}
}
}
67 changes: 0 additions & 67 deletions lib/util/define-unsupported-module-handlers.js

This file was deleted.

39 changes: 39 additions & 0 deletions lib/util/get-configured-node-version.js
@@ -0,0 +1,39 @@
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
"use strict"

const { Range } = require("semver") //eslint-disable-line no-unused-vars
const getPackageJson = require("./get-package-json")
const getSemverRange = require("./get-semver-range")

/**
* Get the `engines.node` field of package.json.
* @param {string} filename The path to the current linting file.
* @returns {Range|null} The range object of the `engines.node` field.
*/
function getEnginesNode(filename) {
const info = getPackageJson(filename)
return getSemverRange(info && info.engines && info.engines.node)
}

/**
* Gets version configuration.
*
* 1. Parse a given version then return it if it's valid.
* 2. Look package.json up and parse `engines.node` then return it if it's valid.
* 3. Return `>=6.0.0`.
*
* @param {string|undefined} version The version range text.
* @param {string} filename The path to the current linting file.
* This will be used to look package.json up if `version` is not a valid version range.
* @returns {Range} The configured version range.
*/
module.exports = function getConfiguredNodeVersion(version, filename) {
return (
getSemverRange(version) ||
getEnginesNode(filename) ||
getSemverRange(">=6.0.0")
)
}
25 changes: 0 additions & 25 deletions lib/util/get-engines-node.js

This file was deleted.

0 comments on commit fa25150

Please sign in to comment.