Skip to content

Commit fa25150

Browse files
committed
Chore: refactoring for no-unsupported-features/*
1 parent 8f25248 commit fa25150

File tree

8 files changed

+188
-150
lines changed

8 files changed

+188
-150
lines changed

lib/rules/no-unsupported-features/es-builtins.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"use strict"
66

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

1111
const trackMap = {
@@ -157,6 +157,10 @@ module.exports = {
157157
},
158158
},
159159
create(context) {
160-
return defineUnsupportedModuleHandlers(context, trackMap)
160+
return {
161+
"Program:exit"() {
162+
checkUnsupportedBuiltins(context, trackMap)
163+
},
164+
}
161165
},
162166
}

lib/rules/no-unsupported-features/es-syntax.js

Lines changed: 46 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

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

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

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

365366
return Object.freeze({ version, ignores })
366367
}
367368

368-
/**
369-
* Define the case selector with given information.
370-
* @param {RuleContext} context The rule context to get scopes.
371-
* @param {string} version The configured version range.
372-
* @param {Node|null} node The node at the current location, for additional conditions.
373-
* @returns {function(aCase:object):boolean} The case selector.
374-
*/
375-
function defineSelector(context, version, node) {
376-
return aCase =>
377-
// Version.
378-
(!aCase.supported ||
379-
semver.intersects(`<${aCase.supported}`, version)) &&
380-
// Additional conditions.
381-
(!aCase.test ||
382-
aCase.test({
383-
node,
384-
get isStrict() {
385-
return Boolean(
386-
node && nomalizeScope(context.getScope(), node).isStrict
387-
)
388-
},
389-
}))
390-
}
391-
392369
/**
393370
* Find the scope that a given node belongs to.
394371
* @param {Scope} initialScope The initial scope to find.
@@ -443,25 +420,47 @@ function dispatch(handlers, node) {
443420
/**
444421
* Define the visitor object as merging the rules of eslint-plugin-es.
445422
* @param {RuleContext} context The rule context.
446-
* @param {{version:string,ignores:Set<string>}} options The options.
423+
* @param {{version:Range,ignores:Set<string>}} options The options.
447424
* @returns {object} The defined visitor.
448425
*/
449426
function defineVisitor(context, options) {
427+
const testInfoPrototype = {
428+
get isStrict() {
429+
return nomalizeScope(context.getScope(), this.node).isStrict
430+
},
431+
}
432+
433+
/**
434+
* Check whether a given case object is full-supported on the configured node version.
435+
* @param {{supported:string}} aCase The case object to check.
436+
* @returns {boolean} `true` if it's supporting.
437+
*/
438+
function isNotSupportingVersion(aCase) {
439+
return (
440+
!aCase.supported ||
441+
options.version.intersects(getSemverRange(`<${aCase.supported}`))
442+
)
443+
}
444+
445+
/**
446+
* Define the predicate function to check whether a given case object is supported on the configured node version.
447+
* @param {Node} node The node which is reported.
448+
* @returns {function(aCase:{supported:string}):boolean} The predicate function.
449+
*/
450+
function isNotSupportingOn(node) {
451+
return aCase =>
452+
isNotSupportingVersion(aCase) &&
453+
(!aCase.test || aCase.test({ node, __proto__: testInfoPrototype }))
454+
}
455+
450456
return (
451457
keywords
452458
// Omit full-supported features and ignored features by options
453459
// because this rule never reports those.
454460
.filter(
455461
keyword =>
456462
!options.ignores.has(keyword) &&
457-
features[keyword].cases.some(
458-
c =>
459-
!c.supported ||
460-
semver.intersects(
461-
options.version,
462-
`<${c.supported}`
463-
)
464-
)
463+
features[keyword].cases.some(isNotSupportingVersion)
465464
)
466465
// Merge remaining features with overriding `context.report()`.
467466
.reduce((visitor, keyword) => {
@@ -476,20 +475,15 @@ function defineVisitor(context, options) {
476475
report(descriptor) {
477476
// Set additional information.
478477
if (descriptor.data) {
479-
descriptor.data.version = options.version
478+
descriptor.data.version = options.version.raw
480479
} else {
481-
descriptor.data = { version: options.version }
480+
descriptor.data = { version: options.version.raw }
482481
}
483482
descriptor.fix = undefined
484483

485484
// Test and report.
486-
const hitCase = cases.find(
487-
defineSelector(
488-
this,
489-
options.version,
490-
descriptor.node
491-
)
492-
)
485+
const node = descriptor.node
486+
const hitCase = cases.find(isNotSupportingOn(node))
493487
if (hitCase) {
494488
descriptor.messageId = hitCase.messageId
495489
descriptor.data.supported = hitCase.supported
@@ -628,8 +622,6 @@ module.exports = {
628622
},
629623
},
630624
create(context) {
631-
const defaultVersion = getEnginesNode(context.getFilename())
632-
const options = parseOptions(context.options[0], defaultVersion)
633-
return defineVisitor(context, options)
625+
return defineVisitor(context, parseOptions(context))
634626
},
635627
}

lib/rules/no-unsupported-features/node-builtins.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"use strict"
66

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

1111
/*eslint-disable camelcase */
@@ -244,6 +244,10 @@ module.exports = {
244244
},
245245
},
246246
create(context) {
247-
return defineUnsupportedModuleHandlers(context, trackMap)
247+
return {
248+
"Program:exit"() {
249+
checkUnsupportedBuiltins(context, trackMap)
250+
},
251+
}
248252
},
249253
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @author Toru Nagashima
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
"use strict"
6+
7+
const { Range } = require("semver") //eslint-disable-line no-unused-vars
8+
const { ReferenceTracker } = require("eslint-utils")
9+
const getConfiguredNodeVersion = require("./get-configured-node-version")
10+
const getSemverRange = require("./get-semver-range")
11+
12+
/**
13+
* Parses the options.
14+
* @param {RuleContext} context The rule context.
15+
* @returns {{version:Range,ignores:Set<string>}} Parsed value.
16+
*/
17+
function parseOptions(context) {
18+
const raw = context.options[0] || {}
19+
const filePath = context.getFilename()
20+
const version = getConfiguredNodeVersion(raw.version, filePath)
21+
const ignores = new Set(raw.ignores || [])
22+
23+
return Object.freeze({ version, ignores })
24+
}
25+
26+
/**
27+
* Verify the code to report unsupported APIs.
28+
* @param {RuleContext} context The rule context.
29+
* @param {{modules:object,globals:object}} trackMap The map for APIs to report.
30+
* @returns {void}
31+
*/
32+
module.exports = function checkUnsupportedBuiltins(context, trackMap) {
33+
const options = parseOptions(context)
34+
const tracker = new ReferenceTracker(context.getScope(), {
35+
mode: "legacy",
36+
})
37+
const references = [
38+
...tracker.iterateCjsReferences(trackMap.modules || {}),
39+
...tracker.iterateEsmReferences(trackMap.modules || {}),
40+
...tracker.iterateGlobalReferences(trackMap.globals || {}),
41+
]
42+
43+
for (const { node, path, info } of references) {
44+
const name = path.join(".")
45+
const supported = options.version.intersects(
46+
getSemverRange(`<${info.supported}`)
47+
)
48+
49+
if (supported && !options.ignores.has(name)) {
50+
context.report({
51+
node,
52+
messageId: "unsupported",
53+
data: {
54+
name,
55+
supported: info.supported,
56+
version: options.version.raw,
57+
},
58+
})
59+
}
60+
}
61+
}

lib/util/define-unsupported-module-handlers.js

Lines changed: 0 additions & 67 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @author Toru Nagashima <https://github.com/mysticatea>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
"use strict"
6+
7+
const { Range } = require("semver") //eslint-disable-line no-unused-vars
8+
const getPackageJson = require("./get-package-json")
9+
const getSemverRange = require("./get-semver-range")
10+
11+
/**
12+
* Get the `engines.node` field of package.json.
13+
* @param {string} filename The path to the current linting file.
14+
* @returns {Range|null} The range object of the `engines.node` field.
15+
*/
16+
function getEnginesNode(filename) {
17+
const info = getPackageJson(filename)
18+
return getSemverRange(info && info.engines && info.engines.node)
19+
}
20+
21+
/**
22+
* Gets version configuration.
23+
*
24+
* 1. Parse a given version then return it if it's valid.
25+
* 2. Look package.json up and parse `engines.node` then return it if it's valid.
26+
* 3. Return `>=6.0.0`.
27+
*
28+
* @param {string|undefined} version The version range text.
29+
* @param {string} filename The path to the current linting file.
30+
* This will be used to look package.json up if `version` is not a valid version range.
31+
* @returns {Range} The configured version range.
32+
*/
33+
module.exports = function getConfiguredNodeVersion(version, filename) {
34+
return (
35+
getSemverRange(version) ||
36+
getEnginesNode(filename) ||
37+
getSemverRange(">=6.0.0")
38+
)
39+
}

lib/util/get-engines-node.js

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)