node_modules

This commit is contained in:
saeid
2019-07-15 13:07:31 +04:30
parent d3735c6d30
commit ce1a3ebf9c
35713 changed files with 3639219 additions and 1 deletions
+11
View File
@@ -0,0 +1,11 @@
# @vue/eslint-config-standard
> eslint-config-standard for vue-cli
This config is specifically designed to be used by `vue-cli` setups
and is not meant for outside use (it can be used but some adaptations
on the user side might be needed - for details see the config file).
A part of its design is that this config may implicitly depend on
other parts of `vue-cli` setups, such as `eslint-plugin-vue` being
extended in the same resulting config.
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
extends: [
require.resolve('eslint-config-standard')
]
}
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Toru Nagashima
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,129 @@
# eslint-plugin-node
[![npm version](https://img.shields.io/npm/v/eslint-plugin-node.svg)](https://www.npmjs.com/package/eslint-plugin-node)
[![Downloads/month](https://img.shields.io/npm/dm/eslint-plugin-node.svg)](http://www.npmtrends.com/eslint-plugin-node)
[![Build Status](https://travis-ci.org/mysticatea/eslint-plugin-node.svg?branch=master)](https://travis-ci.org/mysticatea/eslint-plugin-node)
[![Coverage Status](https://codecov.io/gh/mysticatea/eslint-plugin-node/branch/master/graph/badge.svg)](https://codecov.io/gh/mysticatea/eslint-plugin-node)
[![Dependency Status](https://david-dm.org/mysticatea/eslint-plugin-node.svg)](https://david-dm.org/mysticatea/eslint-plugin-node)
Additional ESLint's rules for Node.js
## 💿 Install & Usage
```
$ npm install --save-dev eslint eslint-plugin-node
```
- Requires Node.js `^4.0.0 || >=6.0.0`
- Requires ESLint `>=3.1.0`
**Note:** It recommends a use of [the "engines" field of package.json](https://docs.npmjs.com/files/package.json#engines). The "engines" field is used by [no-unsupported-features](docs/rules/no-unsupported-features.md) rule.
**.eslintrc.json** (An example)
```json
{
"plugins": ["node"],
"extends": ["eslint:recommended", "plugin:node/recommended"],
"rules": {
"node/exports-style": ["error", "module.exports"]
}
}
```
**package.json** (An example)
```json
{
"name": "your-module",
"version": "1.0.0",
"engines": {
"node": ">=4.0.0"
}
}
```
## 📖 Rules
- ⭐️ - the mark of recommended rules.
- ✒️ - the mark of fixable rules.
<!--RULES_TABLE_START-->
### Possible Errors
| | Rule ID | Description |
|:---|:--------|:------------|
| | [no-extraneous-import](./docs/rules/no-extraneous-import.md) | disallow `import` declarations of extraneous packages |
| ⭐️ | [no-extraneous-require](./docs/rules/no-extraneous-require.md) | disallow `require()` expressions of extraneous packages |
| | [no-missing-import](./docs/rules/no-missing-import.md) | disallow `import` declarations of missing files |
| ⭐️ | [no-missing-require](./docs/rules/no-missing-require.md) | disallow `require()` expressions of missing files |
| ⭐️ | [no-unpublished-bin](./docs/rules/no-unpublished-bin.md) | disallow 'bin' files which are ignored by npm |
| | [no-unpublished-import](./docs/rules/no-unpublished-import.md) | disallow `import` declarations of private things |
| ⭐️ | [no-unpublished-require](./docs/rules/no-unpublished-require.md) | disallow `require()` expressions of private things |
| ⭐️ | [no-unsupported-features](./docs/rules/no-unsupported-features.md) | disallow unsupported ECMAScript features on the specified version |
| ⭐️ | [process-exit-as-throw](./docs/rules/process-exit-as-throw.md) | make `process.exit()` expressions the same code path as `throw` |
| ⭐️✒️ | [shebang](./docs/rules/shebang.md) | enforce the correct usage of shebang |
### Best Practices
| | Rule ID | Description |
|:---|:--------|:------------|
| ⭐️ | [no-deprecated-api](./docs/rules/no-deprecated-api.md) | disallow deprecated APIs |
### Stylistic Issues
| | Rule ID | Description |
|:---|:--------|:------------|
| | [exports-style](./docs/rules/exports-style.md) | enforce either `module.exports` or `exports` |
<!--RULES_TABLE_END-->
## 🔧 Configs
This plugin provides `plugin:node/recommended` preset config.
This preset config:
- enables the environment of ES2015 (ES6) and Node.js.
- enables rules which are given :star: in the above table.
- enables [no-process-exit](http://eslint.org/docs/rules/no-process-exit) rule because [the official document](https://nodejs.org/api/process.html#process_process_exit_code) does not recommend a use of `process.exit()`.
- adds `{ecmaVersion: 8}` into `parserOptions`.
## 👫 FAQ
- Q: The `no-missing-import` / `no-missing-require` rules don't work with nested folders in SublimeLinter-eslint
- A: See [context.getFilename() in rule returns relative path](https://github.com/roadhump/SublimeLinter-eslint#contextgetfilename-in-rule-returns-relative-path) in the SublimeLinter-eslint FAQ.
## 🚥 Semantic Versioning Policy
`eslint-plugin-node` follows [semantic versioning](http://semver.org/) and [ESLint's Semantic Versioning Policy](https://github.com/eslint/eslint#semantic-versioning-policy).
- Patch release (intended to not break your lint build)
- A bug fix in a rule that results in it reporting fewer errors.
- Improvements to documentation.
- Non-user-facing changes such as refactoring code, adding, deleting, or modifying tests, and increasing test coverage.
- Re-releasing after a failed release (i.e., publishing a release that doesn't work for anyone).
- Minor release (might break your lint build)
- A bug fix in a rule that results in it reporting more errors.
- A new rule is created.
- A new option to an existing rule is created.
- An existing rule is deprecated.
- Major release (likely to break your lint build)
- A support for old Node version is dropped.
- A support for old ESLint version is dropped.
- An existing rule is changed in it reporting more errors.
- An existing rule is removed.
- An existing option of a rule is removed.
- An existing config is updated.
## 📰 Changelog
- [GitHub Releases](https://github.com/mysticatea/eslint-plugin-node/releases)
## 💎 Contributing
Welcome contributing!
Please use GitHub's Issues/PRs.
### Development Tools
- `npm test` runs tests and measures coverage.
- `npm run coverage` shows the coverage result of `npm test` command.
- `npm run clean` removes the coverage result of `npm test` command.
@@ -0,0 +1,11 @@
/**
* @author Toru Nagashima
* @copyright 2017 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
module.exports = {
configs: { recommended: require("./recommended.json") },
rules: require("./rules.js"),
}
@@ -0,0 +1,24 @@
{
"parserOptions": {
"ecmaVersion": 8
},
"env": {
"es6": true,
"node": true
},
"rules": {
"no-process-exit": "error",
"node/exports-style": "off",
"node/no-deprecated-api": "error",
"node/no-extraneous-import": "off",
"node/no-extraneous-require": "error",
"node/no-missing-import": "off",
"node/no-missing-require": "error",
"node/no-unpublished-bin": "error",
"node/no-unpublished-import": "off",
"node/no-unpublished-require": "error",
"node/no-unsupported-features": "error",
"node/process-exit-as-throw": "error",
"node/shebang": "error"
}
}
@@ -0,0 +1,22 @@
/**
* @author Toru Nagashima
* @copyright 2017 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
module.exports = {
"exports-style": require("./rules/exports-style"),
"no-deprecated-api": require("./rules/no-deprecated-api"),
"no-extraneous-import": require("./rules/no-extraneous-import"),
"no-extraneous-require": require("./rules/no-extraneous-require"),
"no-hide-core-modules": require("./rules/no-hide-core-modules"),
"no-missing-import": require("./rules/no-missing-import"),
"no-missing-require": require("./rules/no-missing-require"),
"no-unpublished-bin": require("./rules/no-unpublished-bin"),
"no-unpublished-import": require("./rules/no-unpublished-import"),
"no-unpublished-require": require("./rules/no-unpublished-require"),
"no-unsupported-features": require("./rules/no-unsupported-features"),
"process-exit-as-throw": require("./rules/process-exit-as-throw"),
"shebang": require("./rules/shebang"),
}
@@ -0,0 +1,316 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const getDocsUrl = require("../util/get-docs-url")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/*istanbul ignore next */
/**
* This function is copied from https://github.com/eslint/eslint/blob/2355f8d0de1d6732605420d15ddd4f1eee3c37b6/lib/ast-utils.js#L648-L684
*
* @param {ASTNode} node - The node to get.
* @returns {string|null} The property name if static. Otherwise, null.
* @private
*/
function getStaticPropertyName(node) {
let prop = null
switch (node && node.type) {
case "Property":
case "MethodDefinition":
prop = node.key
break
case "MemberExpression":
prop = node.property
break
// no default
}
switch (prop && prop.type) {
case "Literal":
return String(prop.value)
case "TemplateLiteral":
if (prop.expressions.length === 0 && prop.quasis.length === 1) {
return prop.quasis[0].value.cooked
}
break
case "Identifier":
if (!node.computed) {
return prop.name
}
break
// no default
}
return null
}
/**
* Checks whether the given node is assignee or not.
*
* @param {ASTNode} node - The node to check.
* @returns {boolean} `true` if the node is assignee.
*/
function isAssignee(node) {
return (
node.parent.type === "AssignmentExpression" &&
node.parent.left === node
)
}
/**
* Gets the top assignment expression node if the given node is an assignee.
*
* This is used to distinguish 2 assignees belong to the same assignment.
* If the node is not an assignee, this returns null.
*
* @param {ASTNode} leafNode - The node to get.
* @returns {ASTNode|null} The top assignment expression node, or null.
*/
function getTopAssignment(leafNode) {
let node = leafNode
// Skip MemberExpressions.
while (node.parent.type === "MemberExpression" && node.parent.object === node) {
node = node.parent
}
// Check assignments.
if (!isAssignee(node)) {
return null
}
// Find the top.
while (node.parent.type === "AssignmentExpression") {
node = node.parent
}
return node
}
/**
* Gets top assignment nodes of the given node list.
*
* @param {ASTNode[]} nodes - The node list to get.
* @returns {ASTNode[]} Gotten top assignment nodes.
*/
function createAssignmentList(nodes) {
return nodes.map(getTopAssignment).filter(Boolean)
}
/**
* Gets the reference of `module.exports` from the given scope.
*
* @param {escope.Scope} scope - The scope to get.
* @returns {ASTNode[]} Gotten MemberExpression node list.
*/
function getModuleExportsNodes(scope) {
const variable = scope.set.get("module")
if (variable == null) {
return []
}
return variable.references
.map(reference => reference.identifier.parent)
.filter(node => (
node.type === "MemberExpression" &&
getStaticPropertyName(node) === "exports"
))
}
/**
* Gets the reference of `exports` from the given scope.
*
* @param {escope.Scope} scope - The scope to get.
* @returns {ASTNode[]} Gotten Identifier node list.
*/
function getExportsNodes(scope) {
const variable = scope.set.get("exports")
if (variable == null) {
return []
}
return variable.references.map(reference => reference.identifier)
}
/**
* The definition of this rule.
*
* @param {RuleContext} context - The rule context to check.
* @returns {object} The definition of this rule.
*/
function create(context) {
const mode = context.options[0] || "module.exports"
const batchAssignAllowed = Boolean(
context.options[1] != null &&
context.options[1].allowBatchAssign
)
const sourceCode = context.getSourceCode()
/**
* Gets the location info of reports.
*
* exports = foo
* ^^^^^^^^^
*
* module.exports = foo
* ^^^^^^^^^^^^^^^^
*
* @param {ASTNode} node - The node of `exports`/`module.exports`.
* @returns {Location} The location info of reports.
*/
function getLocation(node) {
const token = sourceCode.getTokenAfter(node)
return {
start: node.loc.start,
end: token.loc.end,
}
}
/**
* Enforces `module.exports`.
* This warns references of `exports`.
*
* @returns {void}
*/
function enforceModuleExports() {
const globalScope = context.getScope()
const exportsNodes = getExportsNodes(globalScope)
const assignList = batchAssignAllowed
? createAssignmentList(getModuleExportsNodes(globalScope))
: []
for (const node of exportsNodes) {
// Skip if it's a batch assignment.
if (assignList.length > 0 &&
assignList.indexOf(getTopAssignment(node)) !== -1
) {
continue
}
// Report.
context.report({
node,
loc: getLocation(node),
message:
"Unexpected access to 'exports'. " +
"Use 'module.exports' instead.",
})
}
}
/**
* Enforces `exports`.
* This warns references of `module.exports`.
*
* @returns {void}
*/
function enforceExports() {
const globalScope = context.getScope()
const exportsNodes = getExportsNodes(globalScope)
const moduleExportsNodes = getModuleExportsNodes(globalScope)
const assignList = batchAssignAllowed
? createAssignmentList(exportsNodes)
: []
const batchAssignList = []
for (const node of moduleExportsNodes) {
// Skip if it's a batch assignment.
if (assignList.length > 0) {
const found = assignList.indexOf(getTopAssignment(node))
if (found !== -1) {
batchAssignList.push(assignList[found])
assignList.splice(found, 1)
continue
}
}
// Report.
context.report({
node,
loc: getLocation(node),
message:
"Unexpected access to 'module.exports'. " +
"Use 'exports' instead.",
})
}
// Disallow direct assignment to `exports`.
for (const node of exportsNodes) {
// Skip if it's not assignee.
if (!isAssignee(node)) {
continue
}
// Check if it's a batch assignment.
if (batchAssignList.indexOf(getTopAssignment(node)) !== -1) {
continue
}
// Report.
context.report({
node,
loc: getLocation(node),
message:
"Unexpected assignment to 'exports'. " +
"Don't modify 'exports' itself.",
})
}
}
return {
"Program:exit"() {
switch (mode) {
case "module.exports":
enforceModuleExports()
break
case "exports":
enforceExports()
break
// no default
}
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "enforce either `module.exports` or `exports`",
category: "Stylistic Issues",
recommended: false,
url: getDocsUrl("exports-style.md"),
},
fixable: false,
schema: [
{ //
enum: ["module.exports", "exports"],
},
{
type: "object",
properties: { allowBatchAssign: { type: "boolean" } },
additionalProperties: false,
},
],
},
}
@@ -0,0 +1,597 @@
/**
* @fileoverview Rule to disallow deprecated API.
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const deprecatedApis = require("../util/deprecated-apis")
const getDocsUrl = require("../util/get-docs-url")
const getValueIfString = require("../util/get-value-if-string")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const SENTINEL_TYPE = /^(?:.+?Statement|.+?Declaration|(?:Array|ArrowFunction|Assignment|Call|Class|Function|Member|New|Object)Expression|AssignmentPattern|Program|VariableDeclarator)$/
const MODULE_ITEMS = getDeprecatedItems(deprecatedApis.modules, [], [])
const GLOBAL_ITEMS = getDeprecatedItems(deprecatedApis.globals, [], [])
/**
* Gets the array of deprecated items.
*
* It's the paths which are separated by dots.
* E.g. `buffer.Buffer`, `events.EventEmitter.listenerCount`
*
* @param {object} definition - The definition of deprecated APIs.
* @param {string[]} result - The array of the result.
* @param {string[]} stack - The array to manage the stack of paths.
* @returns {string[]} `result`.
*/
function getDeprecatedItems(definition, result, stack) {
for (const key of Object.keys(definition)) {
const item = definition[key]
if (key === "$call") {
result.push(`${stack.join(".")}()`)
}
else if (key === "$constructor") {
result.push(`new ${stack.join(".")}()`)
}
else {
stack.push(key)
if (item.$deprecated) {
result.push(stack.join("."))
}
else {
getDeprecatedItems(item, result, stack)
}
stack.pop()
}
}
return result
}
/**
* Converts from a version number to a version text to display.
*
* @param {number} value - A version number to convert.
* @returns {string} Covnerted text.
*/
function toVersionText(value) {
if (value <= 0.12) {
return value.toFixed(2)
}
if (value < 1) {
return value.toFixed(1)
}
return String(value)
}
/**
* Makes a replacement message.
*
* @param {string|null} replacedBy - The text of substitute way.
* @returns {string} Replacement message.
*/
function toReplaceMessage(replacedBy) {
return replacedBy ? ` Use ${replacedBy} instead.` : ""
}
/**
* Gets the property name from a MemberExpression node or a Property node.
*
* @param {ASTNode} node - A node to get.
* @returns {string|null} The property name of the node.
*/
function getPropertyName(node) {
switch (node.type) {
case "MemberExpression":
if (node.computed) {
return getValueIfString(node.property)
}
return node.property.name
case "Property":
if (node.computed) {
return getValueIfString(node.key)
}
if (node.key.type === "Literal") {
return String(node.key.value)
}
return node.key.name
// no default
}
/* istanbul ignore next: unreachable */
return null
}
/**
* Checks a given node is a ImportDeclaration node.
*
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is a ImportDeclaration node.
*/
function isImportDeclaration(node) {
return node.type === "ImportDeclaration"
}
/**
* Finds the variable object of a given Identifier node.
*
* @param {ASTNode} node - An Identifier node to find.
* @param {escope.Scope} initialScope - A scope to start searching.
* @returns {escope.Variable} Found variable object.
*/
function findVariable(node, initialScope) {
const location = node.range[0]
let variable = null
// Dive into the scope that the node exists.
for (const childScope of initialScope.childScopes) {
const range = childScope.block.range
if (range[0] <= location && location < range[1]) {
variable = findVariable(node, childScope)
if (variable != null) {
return variable
}
}
}
// Find the variable of that name in this scope or ancestor scopes.
let scope = initialScope
while (scope != null) {
variable = scope.set.get(node.name)
if (variable != null) {
return variable
}
scope = scope.upper
}
return null
}
/**
* Gets the top member expression node.
*
* @param {ASTNode} identifier - The node to get.
* @returns {ASTNode} The top member expression node.
*/
function getTopMemberExpression(identifier) {
if (identifier.type !== "Identifier" && identifier.type !== "Literal") {
return identifier
}
let node = identifier
while (node.parent.type === "MemberExpression") {
node = node.parent
}
return node
}
/**
* The definition of this rule.
*
* @param {RuleContext} context - The rule context to check.
* @returns {object} The definition of this rule.
*/
function create(context) {
const options = context.options[0] || {}
const ignoredModuleItems = options.ignoreModuleItems || []
const ignoredGlobalItems = options.ignoreGlobalItems || []
let globalScope = null
const varStack = []
/**
* Reports a use of a deprecated API.
*
* @param {ASTNode} node - A node to report.
* @param {string} name - The name of a deprecated API.
* @param {{since: number, replacedBy: string}} info - Information of the API.
* @returns {void}
*/
function report(node, name, info) {
context.report({
node,
loc: getTopMemberExpression(node).loc,
message: "{{name}} was deprecated since v{{version}}.{{replace}}",
data: {
name,
version: toVersionText(info.since),
replace: toReplaceMessage(info.replacedBy),
},
})
}
/**
* Reports a use of a deprecated module.
*
* @param {ASTNode} node - A node to report.
* @param {string} name - The name of a deprecated module.
* @param {{since: number, replacedBy: string, global: boolean}} info - Information of the module.
* @returns {void}
*/
function reportModule(node, name, info) {
if (ignoredModuleItems.indexOf(name) === -1) {
report(node, `'${name}' module`, info)
}
}
/**
* Reports a use of a deprecated property.
*
* @param {ASTNode} node - A node to report.
* @param {string[]} path - The path to a deprecated property.
* @param {{since: number, replacedBy: string, global: boolean}} info - Information of the property.
* @returns {void}
*/
function reportCall(node, path, info) {
const ignored = info.global ? ignoredGlobalItems : ignoredModuleItems
const name = `${path.join(".")}()`
if (ignored.indexOf(name) === -1) {
report(node, `'${name}'`, info)
}
}
/**
* Reports a use of a deprecated property.
*
* @param {ASTNode} node - A node to report.
* @param {string[]} path - The path to a deprecated property.
* @param {{since: number, replacedBy: string, global: boolean}} info - Information of the property.
* @returns {void}
*/
function reportConstructor(node, path, info) {
const ignored = info.global ? ignoredGlobalItems : ignoredModuleItems
const name = `new ${path.join(".")}()`
if (ignored.indexOf(name) === -1) {
report(node, `'${name}'`, info)
}
}
/**
* Reports a use of a deprecated property.
*
* @param {ASTNode} node - A node to report.
* @param {string[]} path - The path to a deprecated property.
* @param {string} key - The name of the property.
* @param {{since: number, replacedBy: string, global: boolean}} info - Information of the property.
* @returns {void}
*/
function reportProperty(node, path, key, info) {
const ignored = info.global ? ignoredGlobalItems : ignoredModuleItems
path.push(key)
const name = path.join(".")
path.pop()
if (ignored.indexOf(name) === -1) {
report(node, `'${name}'`, info)
}
}
/**
* Checks violations in destructuring assignments.
*
* @param {ASTNode} node - A pattern node to check.
* @param {string[]} path - The path to a deprecated property.
* @param {object} infoMap - A map of properties' information.
* @returns {void}
*/
function checkDestructuring(node, path, infoMap) {
switch (node.type) {
case "AssignmentPattern":
checkDestructuring(node.left, path, infoMap)
break
case "Identifier": {
const variable = findVariable(node, globalScope)
if (variable != null) {
checkVariable(variable, path, infoMap)
}
break
}
case "ObjectPattern":
for (const property of node.properties) {
const key = getPropertyName(property)
if (key != null && hasOwnProperty.call(infoMap, key)) {
const keyInfo = infoMap[key]
if (keyInfo.$deprecated) {
reportProperty(property.key, path, key, keyInfo)
}
else {
path.push(key)
checkDestructuring(property.value, path, keyInfo)
path.pop()
}
}
}
break
// no default
}
}
/**
* Checks violations in properties.
*
* @param {ASTNode} root - A node to check.
* @param {string[]} path - The path to a deprecated property.
* @param {object} infoMap - A map of properties' information.
* @returns {void}
*/
function checkProperties(root, path, infoMap) { //eslint-disable-line complexity
let node = root
while (!SENTINEL_TYPE.test(node.parent.type)) {
node = node.parent
}
const parent = node.parent
switch (parent.type) {
case "CallExpression":
if (parent.callee === node && infoMap.$call != null) {
reportCall(parent, path, infoMap.$call)
}
break
case "NewExpression":
if (parent.callee === node && infoMap.$constructor != null) {
reportConstructor(parent, path, infoMap.$constructor)
}
break
case "MemberExpression":
if (parent.object === node) {
const key = getPropertyName(parent)
if (key != null && hasOwnProperty.call(infoMap, key)) {
const keyInfo = infoMap[key]
if (keyInfo.$deprecated) {
reportProperty(parent.property, path, key, keyInfo)
}
else {
path.push(key)
checkProperties(parent, path, keyInfo)
path.pop()
}
}
}
break
case "AssignmentExpression":
if (parent.right === node) {
checkDestructuring(parent.left, path, infoMap)
checkProperties(parent, path, infoMap)
}
break
case "AssignmentPattern":
if (parent.right === node) {
checkDestructuring(parent.left, path, infoMap)
}
break
case "VariableDeclarator":
if (parent.init === node) {
checkDestructuring(parent.id, path, infoMap)
}
break
// no default
}
}
/**
* Checks violations in the references of a given variable.
*
* @param {escope.Variable} variable - A variable to check.
* @param {string[]} path - The path to a deprecated property.
* @param {object} infoMap - A map of properties' information.
* @returns {void}
*/
function checkVariable(variable, path, infoMap) {
if (varStack.indexOf(variable) !== -1) {
return
}
varStack.push(variable)
if (infoMap.$deprecated) {
const key = path.pop()
for (const reference of variable.references.filter(r => r.isRead())) {
reportProperty(reference.identifier, path, key, infoMap)
}
}
else {
for (const reference of variable.references.filter(r => r.isRead())) {
checkProperties(reference.identifier, path, infoMap)
}
}
varStack.pop()
}
/**
* Checks violations in a ModuleSpecifier node.
*
* @param {ASTNode} node - A ModuleSpecifier node to check.
* @param {string[]} path - The path to a deprecated property.
* @param {object} infoMap - A map of properties' information.
* @returns {void}
*/
function checkImportSpecifier(node, path, infoMap) {
switch (node.type) {
case "ImportSpecifier": {
const key = node.imported.name
if (hasOwnProperty.call(infoMap, key)) {
const keyInfo = infoMap[key]
if (keyInfo.$deprecated) {
reportProperty(node.imported, path, key, keyInfo)
}
else {
path.push(key)
checkVariable(
findVariable(node.local, globalScope),
path,
keyInfo
)
path.pop()
}
}
break
}
case "ImportDefaultSpecifier":
checkVariable(
findVariable(node.local, globalScope),
path,
infoMap
)
break
case "ImportNamespaceSpecifier":
checkVariable(
findVariable(node.local, globalScope),
path,
Object.assign({}, infoMap, { default: infoMap })
)
break
// no default
}
}
/**
* Checks violations for CommonJS modules.
* @returns {void}
*/
function checkCommonJsModules() {
const infoMap = deprecatedApis.modules
const variable = globalScope.set.get("require")
if (variable == null || variable.defs.length !== 0) {
return
}
for (const reference of variable.references.filter(r => r.isRead())) {
const id = reference.identifier
const node = id.parent
if (node.type === "CallExpression" && node.callee === id) {
const key = getValueIfString(node.arguments[0])
if (key != null && hasOwnProperty.call(infoMap, key)) {
const moduleInfo = infoMap[key]
if (moduleInfo.$deprecated) {
reportModule(node, key, moduleInfo)
}
else {
checkProperties(node, [key], moduleInfo)
}
}
}
}
}
/**
* Checks violations for ES2015 modules.
* @param {ASTNode} programNode - A program node to check.
* @returns {void}
*/
function checkES2015Modules(programNode) {
const infoMap = deprecatedApis.modules
for (const node of programNode.body.filter(isImportDeclaration)) {
const key = node.source.value
if (hasOwnProperty.call(infoMap, key)) {
const moduleInfo = infoMap[key]
if (moduleInfo.$deprecated) {
reportModule(node, key, moduleInfo)
}
else {
for (const specifier of node.specifiers) {
checkImportSpecifier(specifier, [key], moduleInfo)
}
}
}
}
}
/**
* Checks violations for global variables.
* @returns {void}
*/
function checkGlobals() {
const infoMap = deprecatedApis.globals
for (const key of Object.keys(infoMap)) {
const keyInfo = infoMap[key]
const variable = globalScope.set.get(key)
if (variable != null && variable.defs.length === 0) {
checkVariable(variable, [key], keyInfo)
}
}
}
return {
"Program:exit"(node) {
globalScope = context.getScope()
checkCommonJsModules()
checkES2015Modules(node)
checkGlobals()
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "disallow deprecated APIs",
category: "Best Practices",
recommended: true,
url: getDocsUrl("no-deprecated-api.md"),
},
fixable: false,
schema: [
{
type: "object",
properties: {
ignoreModuleItems: {
type: "array",
items: { enum: MODULE_ITEMS },
additionalItems: false,
uniqueItems: true,
},
ignoreGlobalItems: {
type: "array",
items: { enum: GLOBAL_ITEMS },
additionalItems: false,
uniqueItems: true,
},
// Deprecated since v4.2.0
ignoreIndirectDependencies: { type: "boolean" },
},
additionalProperties: false,
},
],
},
}
@@ -0,0 +1,72 @@
/**
* @author Toru Nagashima
* @copyright 2017 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const checkExtraneous = require("../util/check-extraneous")
const getAllowModules = require("../util/get-allow-modules")
const getConvertPath = require("../util/get-convert-path")
const getDocsUrl = require("../util/get-docs-url")
const getImportTargets = require("../util/get-import-export-targets")
const getResolvePaths = require("../util/get-resolve-paths")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* The definition of this rule.
*
* @param {RuleContext} context - The rule context to check.
* @returns {object} The definition of this rule.
*/
function create(context) {
const filePath = context.getFilename()
if (filePath === "<input>") {
return {}
}
return {
"Program:exit"(node) {
checkExtraneous(
context,
filePath,
getImportTargets(context, node, false)
)
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "disallow `import` declarations of extraneous packages",
category: "Possible Errors",
recommended: false,
url: getDocsUrl("no-extraneous-import.md"),
},
fixable: false,
schema: [
{
type: "object",
properties: {
allowModules: getAllowModules.schema,
convertPath: getConvertPath.schema,
resolvePaths: getResolvePaths.schema,
},
additionalProperties: false,
},
],
},
}
@@ -0,0 +1,72 @@
/**
* @author Toru Nagashima
* @copyright 2017 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const checkExtraneous = require("../util/check-extraneous")
const getAllowModules = require("../util/get-allow-modules")
const getConvertPath = require("../util/get-convert-path")
const getDocsUrl = require("../util/get-docs-url")
const getRequireTargets = require("../util/get-require-targets")
const getResolvePaths = require("../util/get-resolve-paths")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* The definition of this rule.
*
* @param {RuleContext} context - The rule context to check.
* @returns {object} The definition of this rule.
*/
function create(context) {
const filePath = context.getFilename()
if (filePath === "<input>") {
return {}
}
return {
"Program:exit"() {
checkExtraneous(
context,
filePath,
getRequireTargets(context, false)
)
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "disallow `require()` expressions of extraneous packages",
category: "Possible Errors",
recommended: true,
url: getDocsUrl("no-extraneous-require.md"),
},
fixable: false,
schema: [
{
type: "object",
properties: {
allowModules: getAllowModules.schema,
convertPath: getConvertPath.schema,
resolvePaths: getResolvePaths.schema,
},
additionalProperties: false,
},
],
},
}
@@ -0,0 +1,131 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*
* @deprecated since v4.2.0
* This rule was based on an invalid assumption.
* No meaning.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const path = require("path")
const resolve = require("resolve")
const getDocsUrl = require("../util/get-docs-url")
const getPackageJson = require("../util/get-package-json")
const getRequireTargets = require("../util/get-require-targets")
const getImportExportTargets = require("../util/get-import-export-targets")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const CORE_MODULES = new Set([
"assert", "buffer", "child_process", "cluster", "console", "constants",
"crypto", "dgram", "dns", /* "domain", */ "events", "fs", "http", "https",
"module", "net", "os", "path", /* "punycode", */ "querystring", "readline",
"repl", "stream", "string_decoder", "timers", "tls", "tty", "url", "util",
"vm", "zlib",
])
const BACK_SLASH = /\\/g
/**
* Creates AST event handlers for no-hide-core-modules.
*
* @param {RuleContext} context - The rule context.
* @returns {object} AST event handlers.
*/
function create(context) {
if (context.getFilename() === "<input>") {
return {}
}
const filePath = path.resolve(context.getFilename())
const dirPath = path.dirname(filePath)
const packageJson = getPackageJson(filePath)
const deps = new Set([].concat(
Object.keys((packageJson && packageJson.dependencies) || {}),
Object.keys((packageJson && packageJson.devDependencies) || {})
))
const options = context.options[0] || {}
const allow = options.allow || []
const ignoreDirectDependencies = Boolean(options.ignoreDirectDependencies)
const ignoreIndirectDependencies = Boolean(options.ignoreIndirectDependencies)
return {
"Program:exit"(node) {
const targets =
[].concat(
getRequireTargets(context, true),
getImportExportTargets(context, node, true)
).filter(t => CORE_MODULES.has(t.moduleName))
for (const target of targets) {
const name = target.moduleName
const allowed =
allow.indexOf(name) !== -1 ||
(ignoreDirectDependencies && deps.has(name)) ||
(ignoreIndirectDependencies && !deps.has(name))
if (allowed) {
continue
}
const resolved = resolve.sync(name, { basedir: dirPath })
const isCore = resolved === name
if (isCore) {
continue
}
context.report({
node: target.node,
loc: target.node.loc,
message: "Unexpected import of third-party module '{{name}}'.",
data: {
name: path
.relative(dirPath, resolved)
.replace(BACK_SLASH, "/"),
},
})
}
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "disallow third-party modules which are hiding core modules",
category: "Possible Errors",
recommended: false,
url: getDocsUrl("no-hide-core-modules.md"),
},
deprecated: true,
fixable: false,
schema: [
{
type: "object",
properties: {
allow: {
type: "array",
items: { enum: Array.from(CORE_MODULES) },
additionalItems: false,
uniqueItems: true,
},
ignoreDirectDependencies: { type: "boolean" },
ignoreIndirectDependencies: { type: "boolean" },
},
additionalProperties: false,
},
],
},
}
@@ -0,0 +1,71 @@
/**
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const checkExistence = require("../util/check-existence")
const getAllowModules = require("../util/get-allow-modules")
const getDocsUrl = require("../util/get-docs-url")
const getImportExportTargets = require("../util/get-import-export-targets")
const getResolvePaths = require("../util/get-resolve-paths")
const getTryExtensions = require("../util/get-try-extensions")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* The definition of this rule.
*
* @param {RuleContext} context - The rule context to check.
* @returns {object} The definition of this rule.
*/
function create(context) {
const filePath = context.getFilename()
if (filePath === "<input>") {
return {}
}
return {
"Program:exit"(node) {
checkExistence(
context,
getImportExportTargets(context, node)
)
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "disallow `import` declarations of missing files",
category: "Possible Errors",
recommended: false,
url: getDocsUrl("no-missing-import.md"),
},
fixable: false,
schema: [
{
type: "object",
properties: {
allowModules: getAllowModules.schema,
tryExtensions: getTryExtensions.schema,
resolvePaths: getResolvePaths.schema,
},
additionalProperties: false,
},
],
},
}
@@ -0,0 +1,71 @@
/**
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const checkExistence = require("../util/check-existence")
const getAllowModules = require("../util/get-allow-modules")
const getDocsUrl = require("../util/get-docs-url")
const getRequireTargets = require("../util/get-require-targets")
const getResolvePaths = require("../util/get-resolve-paths")
const getTryExtensions = require("../util/get-try-extensions")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* The definition of this rule.
*
* @param {RuleContext} context - The rule context to check.
* @returns {object} The definition of this rule.
*/
function create(context) {
const filePath = context.getFilename()
if (filePath === "<input>") {
return {}
}
return {
"Program:exit"() {
checkExistence(
context,
getRequireTargets(context)
)
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "disallow `require()` expressions of missing files",
category: "Possible Errors",
recommended: true,
url: getDocsUrl("no-missing-require.md"),
},
fixable: false,
schema: [
{
type: "object",
properties: {
allowModules: getAllowModules.schema,
tryExtensions: getTryExtensions.schema,
resolvePaths: getResolvePaths.schema,
},
additionalProperties: false,
},
],
},
}
@@ -0,0 +1,115 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const path = require("path")
const getConvertPath = require("../util/get-convert-path")
const getDocsUrl = require("../util/get-docs-url")
const getNpmignore = require("../util/get-npmignore")
const getPackageJson = require("../util/get-package-json")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Checks whether or not a given path is a `bin` file.
*
* @param {string} filePath - A file path to check.
* @param {string|object|undefined} binField - A value of the `bin` field of `package.json`.
* @param {string} basedir - A directory path that `package.json` exists.
* @returns {boolean} `true` if the file is a `bin` file.
*/
function isBinFile(filePath, binField, basedir) {
if (!binField) {
return false
}
if (typeof binField === "string") {
return filePath === path.resolve(basedir, binField)
}
return Object.keys(binField).some(key => filePath === path.resolve(basedir, binField[key]))
}
/**
* The definition of this rule.
*
* @param {RuleContext} context - The rule context to check.
* @returns {object} The definition of this rule.
*/
function create(context) {
return {
Program(node) {
// Check file path.
let rawFilePath = context.getFilename()
if (rawFilePath === "<input>") {
return
}
rawFilePath = path.resolve(rawFilePath)
// Find package.json
const p = getPackageJson(rawFilePath)
if (!p) {
return
}
// Convert by convertPath option
const basedir = path.dirname(p.filePath)
const relativePath = getConvertPath(context)(
path.relative(basedir, rawFilePath).replace(/\\/g, "/")
)
const filePath = path.join(basedir, relativePath)
// Check this file is bin.
if (!isBinFile(filePath, p.bin, basedir)) {
return
}
// Check ignored or not
const npmignore = getNpmignore(filePath)
if (!npmignore.match(relativePath)) {
return
}
// Report.
context.report({
node,
message:
"npm ignores '{{name}}'. " +
"Check 'files' field of 'package.json' or '.npmignore'.",
data: { name: relativePath },
})
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "disallow 'bin' files which are ignored by npm",
category: "Possible Errors",
recommended: true,
url: getDocsUrl("no-unpublished-bin.md"),
},
fixable: false,
schema: [
{
type: "object",
properties: { //
convertPath: getConvertPath.schema,
},
},
],
},
}
@@ -0,0 +1,74 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const checkPublish = require("../util/check-publish")
const getAllowModules = require("../util/get-allow-modules")
const getConvertPath = require("../util/get-convert-path")
const getDocsUrl = require("../util/get-docs-url")
const getImportExportTargets = require("../util/get-import-export-targets")
const getResolvePaths = require("../util/get-resolve-paths")
const getTryExtensions = require("../util/get-try-extensions")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* The definition of this rule.
*
* @param {RuleContext} context - The rule context to check.
* @returns {object} The definition of this rule.
*/
function create(context) {
const filePath = context.getFilename()
if (filePath === "<input>") {
return {}
}
return {
"Program:exit"(node) {
checkPublish(
context,
filePath,
getImportExportTargets(context, node)
)
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "disallow `import` declarations of private things",
category: "Possible Errors",
recommended: false,
url: getDocsUrl("no-unpublished-import.md"),
},
fixable: false,
schema: [
{
type: "object",
properties: {
allowModules: getAllowModules.schema,
convertPath: getConvertPath.schema,
resolvePaths: getResolvePaths.schema,
tryExtensions: getTryExtensions.schema,
},
additionalProperties: false,
},
],
},
}
@@ -0,0 +1,74 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const checkPublish = require("../util/check-publish")
const getAllowModules = require("../util/get-allow-modules")
const getConvertPath = require("../util/get-convert-path")
const getDocsUrl = require("../util/get-docs-url")
const getRequireTargets = require("../util/get-require-targets")
const getResolvePaths = require("../util/get-resolve-paths")
const getTryExtensions = require("../util/get-try-extensions")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* The definition of this rule.
*
* @param {RuleContext} context - The rule context to check.
* @returns {object} The definition of this rule.
*/
function create(context) {
const filePath = context.getFilename()
if (filePath === "<input>") {
return {}
}
return {
"Program:exit"() {
checkPublish(
context,
filePath,
getRequireTargets(context)
)
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "disallow `require()` expressions of private things",
category: "Possible Errors",
recommended: true,
url: getDocsUrl("no-unpublished-require.md"),
},
fixable: false,
schema: [
{
type: "object",
properties: {
allowModules: getAllowModules.schema,
convertPath: getConvertPath.schema,
resolvePaths: getResolvePaths.schema,
tryExtensions: getTryExtensions.schema,
},
additionalProperties: false,
},
],
},
}
@@ -0,0 +1,801 @@
/**
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const semver = require("semver")
const features = require("../util/features")
const getDocsUrl = require("../util/get-docs-url")
const getPackageJson = require("../util/get-package-json")
const getValueIfString = require("../util/get-value-if-string")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const VERSION_MAP = new Map([
[0.1, "0.10.0"],
[0.12, "0.12.0"],
[4, "4.0.0"],
[5, "5.0.0"],
[6, "6.0.0"],
[6.5, "6.5.0"],
[7, "7.0.0"],
[7.6, "7.6.0"],
[8, "8.0.0"],
[8.3, "8.3.0"],
[9, "9.0.0"],
[10, "10.0.0"],
])
const VERSION_SCHEMA = {
anyOf: [
{ enum: Array.from(VERSION_MAP.keys()) },
{
type: "string",
pattern: "^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)$",
},
],
}
const DEFAULT_VERSION = "4.0.0"
const OPTIONS = Object.keys(features)
const FUNC_TYPE = /^(?:Arrow)?Function(?:Declaration|Expression)$/
const CLASS_TYPE = /^Class(?:Declaration|Expression)$/
const DESTRUCTURING_PARENT_TYPE = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression|AssignmentExpression|VariableDeclarator)$/
const TOPLEVEL_SCOPE_TYPE = /^(?:global|function|module)$/
const BINARY_NUMBER = /^0[bB]/
const OCTAL_NUMBER = /^0[oO]/
const UNICODE_ESC = /(\\+)u\{[0-9a-fA-F]+?\}/g
const GET_OR_SET = /^(?:g|s)et$/
const NEW_BUILTIN_TYPES = [
"Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array",
"Int32Array", "Uint32Array", "Float32Array", "Float64Array", "DataView",
"Map", "Set", "WeakMap", "WeakSet", "Proxy", "Reflect", "Promise", "Symbol",
"SharedArrayBuffer", "Atomics",
]
const SUBCLASSING_TEST_TARGETS = [
"Array", "RegExp", "Function", "Promise", "Boolean", "Number", "String",
"Map", "Set",
]
const PROPERTY_TEST_TARGETS = {
Object: [
"assign", "is", "getOwnPropertySymbols", "setPrototypeOf", "values",
"entries", "getOwnPropertyDescriptors",
],
String: ["raw", "fromCodePoint"],
Array: ["from", "of"],
Number: [
"isFinite", "isInteger", "isSafeInteger", "isNaN", "EPSILON",
"MIN_SAFE_INTEGER", "MAX_SAFE_INTEGER",
],
Math: [
"clz32", "imul", "sign", "log10", "log2", "log1p", "expm1", "cosh",
"sinh", "tanh", "acosh", "asinh", "atanh", "trunc", "fround", "cbrt",
"hypot",
],
Symbol: [
"hasInstance", "isConcatSpreadablec", "iterator", "species", "replace",
"search", "split", "match", "toPrimitive", "toStringTag", "unscopables",
],
Atomics: [
"add", "and", "compareExchange", "exchange", "wait", "wake",
"isLockFree", "load", "or", "store", "sub", "xor",
],
}
const REGEXP_NAMED_GROUP = /(\\*)\(\?<[_$\w]/
const REGEXP_LOOKBEHIND = /(\\*)\(\?<[=!]/
const REGEXP_UNICODE_PROPERTY = /(\\*)\\[pP]{.+?}/
/**
* Gets default version configuration of this rule.
*
* This finds and reads 'package.json' file, then parses 'engines.node' field.
* If it's nothing, this returns null.
*
* @param {string} filename - The file name of the current linting file.
* @returns {string} The default version configuration.
*/
function getDefaultVersion(filename) {
const info = getPackageJson(filename)
const nodeVersion = info && info.engines && info.engines.node
return semver.validRange(nodeVersion) || DEFAULT_VERSION
}
/**
* Gets values of the `ignores` option.
*
* @returns {string[]} Values of the `ignores` option.
*/
function getIgnoresEnum() {
return Object.keys(OPTIONS.reduce(
(retv, key) => {
for (const alias of features[key].alias) {
retv[alias] = true
}
retv[key] = true
return retv
},
Object.create(null)
))
}
/**
* Checks whether a given key should be ignored or not.
*
* @param {string} key - A key to check.
* @param {string[]} ignores - An array of keys and aliases to be ignored.
* @returns {boolean} `true` if the key should be ignored.
*/
function isIgnored(key, ignores) {
return (
ignores.indexOf(key) !== -1 ||
features[key].alias.some(alias => ignores.indexOf(alias) !== -1)
)
}
/**
* Parses the options.
*
* @param {number|string|object|undefined} options - An option object to parse.
* @param {number} defaultVersion - The default version to use if the version option was omitted.
* @returns {object} Parsed value.
*/
function parseOptions(options, defaultVersion) {
let version = null
let range = null
let ignores = []
if (typeof options === "number") {
version = VERSION_MAP.get(options)
}
else if (typeof options === "string") {
version = options
}
else if (typeof options === "object") {
version = (typeof options.version === "number")
? VERSION_MAP.get(options.version)
: options.version
ignores = options.ignores || []
}
range = semver.validRange(version ? `>=${version}` : defaultVersion)
if (!version) {
version = defaultVersion
}
return Object.freeze({
version,
features: Object.freeze(OPTIONS.reduce(
(retv, key) => {
const feature = features[key]
if (isIgnored(key, ignores)) {
retv[key] = Object.freeze({
name: feature.name,
singular: Boolean(feature.singular),
supported: true,
supportedInStrict: true,
})
}
else if (typeof feature.node === "string") {
retv[key] = Object.freeze({
name: feature.name,
singular: Boolean(feature.singular),
supported: !semver.intersects(range, `<${feature.node}`),
supportedInStrict: !semver.intersects(range, `<${feature.node}`),
})
}
else {
retv[key] = Object.freeze({
name: feature.name,
singular: Boolean(feature.singular),
supported:
feature.node != null &&
feature.node.sloppy != null &&
!semver.intersects(range, `<${feature.node.sloppy}`),
supportedInStrict:
feature.node != null &&
feature.node.strict != null &&
!semver.intersects(range, `<${feature.node.strict}`),
})
}
return retv
},
Object.create(null)
)),
})
}
/**
* Checks whether or not the current configure has a special lexical environment.
* If it's modules or globalReturn then it has a special lexical environment.
*
* @param {RuleContext} context - A context to check.
* @returns {boolean} `true` if the current configure is modules or globalReturn.
*/
function checkSpecialLexicalEnvironment(context) {
const parserOptions = context.parserOptions
const ecmaFeatures = parserOptions.ecmaFeatures
return Boolean(
parserOptions.sourceType === "module" ||
(ecmaFeatures && ecmaFeatures.globalReturn)
)
}
/**
* Gets the name of a given node.
*
* @param {ASTNode} node - An Identifier node to get.
* @returns {string} The name of the node.
*/
function getIdentifierName(node) {
return node.name
}
/**
* Checks whether the given string has `\u{90ABCDEF}`-like escapes.
*
* @param {string} raw - The string to check.
* @returns {boolean} `true` if the string has Unicode code point escapes.
*/
function hasUnicodeCodePointEscape(raw) {
let match = null
UNICODE_ESC.lastIndex = 0
while ((match = UNICODE_ESC.exec(raw)) != null) {
if (match[1].length % 2 === 1) {
return true
}
}
return false
}
/**
* Check a given string has a given pattern.
* @param {string} s A string to check.
* @param {RegExp} pattern A RegExp object to check.
* @returns {boolean} `true` if the string has the pattern.
*/
function hasPattern(s, pattern) {
const m = pattern.exec(s)
return m != null && ((m[1] || "").length % 2) === 0
}
/**
* The definition of this rule.
*
* @param {RuleContext} context - The rule context to check.
* @returns {object} The definition of this rule.
*/
function create(context) {
const sourceCode = context.getSourceCode()
const supportInfo = parseOptions(
context.options[0],
getDefaultVersion(context.getFilename())
)
const hasSpecialLexicalEnvironment = checkSpecialLexicalEnvironment(context)
/**
* Gets the references of the specified global variables.
*
* @param {string[]} names - Variable names to get.
* @returns {void}
*/
function* getReferences(names) {
const globalScope = context.getScope()
for (const name of names) {
const variable = globalScope.set.get(name)
if (variable && variable.defs.length === 0) {
yield* variable.references
}
}
}
/**
* Checks whether or not the current scope is strict mode.
*
* @returns {boolean}
* `true` if the current scope is strict mode. Otherwise `false`.
*/
function isStrict() {
let scope = context.getScope()
if (scope.type === "global" && hasSpecialLexicalEnvironment) {
scope = scope.childScopes[0]
}
return scope.isStrict
}
/**
* Checks whether the given function has trailing commas or not.
*
* @param {ASTNode} node - The function node to check.
* @returns {boolean} `true` if the function has trailing commas.
*/
function hasTrailingCommaForFunction(node) {
const length = node.params.length
return (
length >= 1 &&
sourceCode.getTokenAfter(node.params[length - 1]).value === ","
)
}
/**
* Checks whether the given call expression has trailing commas or not.
*
* @param {ASTNode} node - The call expression node to check.
* @returns {boolean} `true` if the call expression has trailing commas.
*/
function hasTrailingCommaForCall(node) {
return (
node.arguments.length >= 1 &&
sourceCode.getLastToken(node, 1).value === ","
)
}
/**
* Checks whether the given class extends from null or not.
*
* @param {ASTNode} node - The class node to check.
* @returns {boolean} `true` if the class extends from null.
*/
function extendsNull(node) {
return (
node.superClass != null &&
node.superClass.type === "Literal" &&
node.superClass.value === null
)
}
/**
* Reports a given node if the specified feature is not supported.
*
* @param {ASTNode} node - A node to be reported.
* @param {string} key - A feature name to report.
* @returns {void}
*/
function report(node, key) {
const version = supportInfo.version
const feature = supportInfo.features[key]
if (feature.supported) {
return
}
if (!feature.supportedInStrict) {
context.report({
node,
message: "{{feature}} {{be}} not supported yet on Node {{version}}.",
data: {
feature: feature.name,
be: feature.singular ? "is" : "are",
version,
},
})
}
else if (!isStrict()) {
context.report({
node,
message: "{{feature}} {{be}} not supported yet on Node {{version}}.",
data: {
feature: `${feature.name} in non-strict mode`,
be: feature.singular ? "is" : "are",
version,
},
})
}
}
/**
* Validate RegExp syntax.
* @param {string} pattern A RegExp pattern to check.
* @param {string} flags A RegExp flags to check.
* @param {ASTNode} node A node to report.
* @returns {void}
*/
function validateRegExp(pattern, flags, node) {
if (typeof pattern === "string") {
if (hasPattern(pattern, REGEXP_NAMED_GROUP)) {
report(node, "regexpNamedCaptureGroups")
}
if (hasPattern(pattern, REGEXP_LOOKBEHIND)) {
report(node, "regexpLookbehind")
}
if (hasPattern(pattern, REGEXP_UNICODE_PROPERTY)) {
report(node, "regexpUnicodeProperties")
}
}
if (typeof flags === "string") {
if (flags.indexOf("y") !== -1) {
report(node, "regexpY")
}
if (flags.indexOf("u") !== -1) {
report(node, "regexpU")
}
if (flags.indexOf("s") !== -1) {
report(node, "regexpS")
}
}
}
/**
* Validate RegExp syntax in a RegExp literal.
* @param {ASTNode} node A Literal node to check.
* @returns {void}
*/
function validateRegExpLiteral(node) {
validateRegExp(node.regex.pattern, node.regex.flags, node)
}
/**
* Validate RegExp syntax in the first argument of `new RegExp()`.
* @param {ASTNode} node A NewExpression node to check.
* @returns {void}
*/
function validateRegExpString(node) {
const patternNode = node.arguments[0]
const flagsNode = node.arguments[1]
const pattern = (patternNode && patternNode.type === "Literal" && typeof patternNode.value === "string") ? patternNode.value : null
const flags = (flagsNode && flagsNode.type === "Literal" && typeof flagsNode.value === "string") ? flagsNode.value : null
validateRegExp(pattern, flags, node)
}
return {
//----------------------------------------------------------------------
// Program
//----------------------------------------------------------------------
//eslint-disable-next-line complexity
"Program:exit"() {
// Check new global variables.
for (const name of NEW_BUILTIN_TYPES) {
for (const reference of getReferences([name])) {
// Ignore if it's using new static methods.
const node = reference.identifier
const parentNode = node.parent
const properties = PROPERTY_TEST_TARGETS[name]
if (properties && parentNode.type === "MemberExpression") {
const propertyName = (parentNode.computed ? getValueIfString : getIdentifierName)(parentNode.property)
if (properties.indexOf(propertyName) !== -1) {
continue
}
}
report(reference.identifier, name)
}
}
// Check static methods.
for (const reference of getReferences(Object.keys(PROPERTY_TEST_TARGETS))) {
const node = reference.identifier
const parentNode = node.parent
if (parentNode.type !== "MemberExpression" ||
parentNode.object !== node
) {
continue
}
const objectName = node.name
const properties = PROPERTY_TEST_TARGETS[objectName]
const propertyName = (parentNode.computed ? getValueIfString : getIdentifierName)(parentNode.property)
if (propertyName && properties.indexOf(propertyName) !== -1) {
report(parentNode, `${objectName}.${propertyName}`)
}
}
// Check subclassing
for (const reference of getReferences(SUBCLASSING_TEST_TARGETS)) {
const node = reference.identifier
const parentNode = node.parent
if (CLASS_TYPE.test(parentNode.type) &&
parentNode.superClass === node
) {
report(node, `extends${node.name}`)
}
}
},
//----------------------------------------------------------------------
// Functions
//----------------------------------------------------------------------
"ArrowFunctionExpression"(node) {
report(node, "arrowFunctions")
if (node.async) {
report(node, "asyncAwait")
}
if (hasTrailingCommaForFunction(node)) {
report(node, "trailingCommasInFunctions")
}
},
"AssignmentPattern"(node) {
if (FUNC_TYPE.test(node.parent.type)) {
report(node, "defaultParameters")
}
},
"FunctionDeclaration"(node) {
const scope = context.getScope().upper
if (!TOPLEVEL_SCOPE_TYPE.test(scope.type)) {
report(node, "blockScopedFunctions")
}
if (node.generator) {
report(node, "generatorFunctions")
}
if (node.async) {
report(node, "asyncAwait")
}
if (hasTrailingCommaForFunction(node)) {
report(node, "trailingCommasInFunctions")
}
if (node.async && node.generator) {
report(node, "asyncGenerators")
}
},
"FunctionExpression"(node) {
if (node.generator) {
report(node, "generatorFunctions")
}
if (node.async) {
report(node, "asyncAwait")
}
if (hasTrailingCommaForFunction(node)) {
report(node, "trailingCommasInFunctions")
}
if (node.async && node.generator) {
report(node, "asyncGenerators")
}
},
"MetaProperty"(node) {
const meta = node.meta.name || node.meta
const property = node.property.name || node.property
if (meta === "new" && property === "target") {
report(node, "new.target")
}
},
//----------------------------------------------------------------------
// Classes
//----------------------------------------------------------------------
"ClassDeclaration"(node) {
report(node, "classes")
if (extendsNull(node)) {
report(node, "extendsNull")
}
},
"ClassExpression"(node) {
report(node, "classes")
if (extendsNull(node)) {
report(node, "extendsNull")
}
},
//----------------------------------------------------------------------
// Statements
//----------------------------------------------------------------------
"ForOfStatement"(node) {
report(node, "forOf")
if (node.await) {
report(node, "forAwaitOf")
}
},
"VariableDeclaration"(node) {
if (node.kind === "const") {
report(node, "const")
}
else if (node.kind === "let") {
report(node, "let")
}
},
//----------------------------------------------------------------------
// Expressions
//----------------------------------------------------------------------
"ArrayPattern"(node) {
if (DESTRUCTURING_PARENT_TYPE.test(node.parent.type)) {
report(node, "destructuring")
}
},
"AssignmentExpression"(node) {
if (node.operator === "**=") {
report(node, "exponentialOperators")
}
},
"AwaitExpression"(node) {
report(node, "asyncAwait")
},
"BinaryExpression"(node) {
if (node.operator === "**") {
report(node, "exponentialOperators")
}
},
"CallExpression"(node) {
if (hasTrailingCommaForCall(node)) {
report(node, "trailingCommasInFunctions")
}
},
"Identifier"(node) {
const raw = sourceCode.getText(node)
if (hasUnicodeCodePointEscape(raw)) {
report(node, "unicodeCodePointEscapes")
}
},
"Literal"(node) {
if (typeof node.value === "number") {
if (BINARY_NUMBER.test(node.raw)) {
report(node, "binaryNumberLiterals")
}
else if (OCTAL_NUMBER.test(node.raw)) {
report(node, "octalNumberLiterals")
}
}
else if (typeof node.value === "string") {
if (hasUnicodeCodePointEscape(node.raw)) {
report(node, "unicodeCodePointEscapes")
}
}
else if (node.regex) {
validateRegExpLiteral(node)
}
},
"NewExpression"(node) {
if (node.callee.type === "Identifier" && node.callee.name === "RegExp") {
validateRegExpString(node)
}
if (hasTrailingCommaForCall(node)) {
report(node, "trailingCommasInFunctions")
}
},
"ObjectPattern"(node) {
if (DESTRUCTURING_PARENT_TYPE.test(node.parent.type)) {
report(node, "destructuring")
}
},
"Property"(node) {
if (node.parent.type === "ObjectExpression" &&
(node.computed || node.shorthand || node.method)
) {
if (node.shorthand && GET_OR_SET.test(node.key.name)) {
report(node, "objectPropertyShorthandOfGetSet")
}
else {
report(node, "objectLiteralExtensions")
}
}
},
"RestElement"(node) {
if (FUNC_TYPE.test(node.parent.type)) {
report(node, "restParameters")
}
else if (node.parent.type === "ObjectPattern") {
report(node, "restProperties")
}
},
"SpreadElement"(node) {
if (node.parent.type === "ObjectExpression") {
report(node, "spreadProperties")
}
else {
report(node, "spreadOperators")
}
},
"TemplateElement"(node) {
if (node.value.cooked == null) {
report(node, "templateLiteralRevision")
}
},
"TemplateLiteral"(node) {
report(node, "templateStrings")
},
//----------------------------------------------------------------------
// Legacy
//----------------------------------------------------------------------
"ExperimentalRestProperty"(node) {
report(node, "restProperties")
},
"ExperimentalSpreadProperty"(node) {
report(node, "spreadProperties")
},
"RestProperty"(node) {
report(node, "restProperties")
},
"SpreadProperty"(node) {
report(node, "spreadProperties")
},
//----------------------------------------------------------------------
// Modules
//----------------------------------------------------------------------
"ExportAllDeclaration"(node) {
report(node, "modules")
},
"ExportDefaultDeclaration"(node) {
report(node, "modules")
},
"ExportNamedDeclaration"(node) {
report(node, "modules")
},
"ImportDeclaration"(node) {
report(node, "modules")
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "disallow unsupported ECMAScript features on the specified version",
category: "Possible Errors",
recommended: true,
url: getDocsUrl("no-unsupported-features.md"),
},
fixable: false,
schema: [
{
anyOf: [
VERSION_SCHEMA.anyOf[0],
VERSION_SCHEMA.anyOf[1],
{
type: "object",
properties: {
version: VERSION_SCHEMA,
ignores: {
type: "array",
items: { enum: getIgnoresEnum() },
uniqueItems: true,
},
},
additionalProperties: false,
},
],
},
],
},
}
@@ -0,0 +1,162 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const CodePathAnalyzer = safeRequire("eslint/lib/code-path-analysis/code-path-analyzer")
const CodePath = safeRequire("eslint/lib/code-path-analysis/code-path")
const CodePathSegment = safeRequire("eslint/lib/code-path-analysis/code-path-segment")
const getDocsUrl = require("../util/get-docs-url")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const originalLeaveNode = CodePathAnalyzer && CodePathAnalyzer.prototype.leaveNode
/**
* Imports a specific module.
*
* @param {string} moduleName - A module name to import.
* @returns {object|null} The imported object, or null.
*/
function safeRequire(moduleName) {
try {
return require(moduleName)
}
catch (_err) {
return null
}
}
/* istanbul ignore next */
/**
* Copied from https://github.com/eslint/eslint/blob/16fad5880bb70e9dddbeab8ed0f425ae51f5841f/lib/code-path-analysis/code-path-analyzer.js#L137
*
* @param {CodePathAnalyzer} analyzer - The instance.
* @param {ASTNode} node - The current AST node.
* @returns {void}
*/
function forwardCurrentToHead(analyzer, node) {
const codePath = analyzer.codePath
const state = CodePath.getState(codePath)
const currentSegments = state.currentSegments
const headSegments = state.headSegments
const end = Math.max(currentSegments.length, headSegments.length)
let i = 0
let currentSegment = null
let headSegment = null
// Fires leaving events.
for (i = 0; i < end; ++i) {
currentSegment = currentSegments[i]
headSegment = headSegments[i]
if (currentSegment !== headSegment && currentSegment) {
if (currentSegment.reachable) {
analyzer.emitter.emit(
"onCodePathSegmentEnd",
currentSegment,
node
)
}
}
}
// Update state.
state.currentSegments = headSegments
// Fires entering events.
for (i = 0; i < end; ++i) {
currentSegment = currentSegments[i]
headSegment = headSegments[i]
if (currentSegment !== headSegment && headSegment) {
CodePathSegment.markUsed(headSegment)
if (headSegment.reachable) {
analyzer.emitter.emit(
"onCodePathSegmentStart",
headSegment,
node
)
}
}
}
}
/**
* Checks whether a given node is `process.exit()` or not.
*
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is `process.exit()`.
*/
function isProcessExit(node) {
return (
node.type === "CallExpression" &&
node.callee.type === "MemberExpression" &&
node.callee.computed === false &&
node.callee.object.type === "Identifier" &&
node.callee.object.name === "process" &&
node.callee.property.type === "Identifier" &&
node.callee.property.name === "exit"
)
}
/**
* The function to override `CodePathAnalyzer.prototype.leaveNode` in order to
* address `process.exit()` as throw.
*
* @this CodePathAnalyzer
* @param {ASTNode} node - A node to be left.
* @returns {void}
*/
function overrideLeaveNode(node) {
if (isProcessExit(node)) {
this.currentNode = node
forwardCurrentToHead(this, node)
CodePath.getState(this.codePath).makeThrow()
this.original.leaveNode(node)
this.currentNode = null
}
else {
originalLeaveNode.call(this, node)
}
}
const visitor = CodePathAnalyzer == null ? {} : {
"Program": function installProcessExitAsThrow() {
CodePathAnalyzer.prototype.leaveNode = overrideLeaveNode
},
"Program:exit": function restoreProcessExitAsThrow() {
CodePathAnalyzer.prototype.leaveNode = originalLeaveNode
},
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "make `process.exit()` expressions the same code path as `throw`",
category: "Possible Errors",
recommended: true,
url: getDocsUrl("process-exit-as-throw.md"),
},
fixable: false,
schema: [],
supported: CodePathAnalyzer != null,
},
create() {
return visitor
},
}
@@ -0,0 +1,163 @@
/**
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const path = require("path")
const getConvertPath = require("../util/get-convert-path")
const getDocsUrl = require("../util/get-docs-url")
const getPackageJson = require("../util/get-package-json")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const NODE_SHEBANG = "#!/usr/bin/env node\n"
const SHEBANG_PATTERN = /^(#!.+?)?(\r)?\n/
const NODE_SHEBANG_PATTERN = /#!\/usr\/bin\/env node(?: [^\r\n]+?)?\n/
/**
* Checks whether or not a given path is a `bin` file.
*
* @param {string} filePath - A file path to check.
* @param {string|object|undefined} binField - A value of the `bin` field of `package.json`.
* @param {string} basedir - A directory path that `package.json` exists.
* @returns {boolean} `true` if the file is a `bin` file.
*/
function isBinFile(filePath, binField, basedir) {
if (!binField) {
return false
}
if (typeof binField === "string") {
return filePath === path.resolve(basedir, binField)
}
return Object.keys(binField).some(key => filePath === path.resolve(basedir, binField[key]))
}
/**
* Gets the shebang line (includes a line ending) from a given code.
*
* @param {SourceCode} sourceCode - A source code object to check.
* @returns {{length: number, bom: boolean, shebang: string, cr: boolean}}
* shebang's information.
* `retv.shebang` is an empty string if shebang doesn't exist.
*/
function getShebangInfo(sourceCode) {
const m = SHEBANG_PATTERN.exec(sourceCode.text)
return {
bom: sourceCode.hasBOM,
cr: Boolean(m && m[2]),
length: (m && m[0].length) || 0,
shebang: (m && m[1] && (`${m[1]}\n`)) || "",
}
}
/**
* The definition of this rule.
*
* @param {RuleContext} context - The rule context to check.
* @returns {object} The definition of this rule.
*/
function create(context) {
const sourceCode = context.getSourceCode()
let filePath = context.getFilename()
if (filePath === "<input>") {
return {}
}
filePath = path.resolve(filePath)
const p = getPackageJson(filePath)
if (!p) {
return {}
}
const basedir = path.dirname(p.filePath)
filePath = path.join(
basedir,
getConvertPath(context)(path.relative(basedir, filePath).replace(/\\/g, "/"))
)
const needsShebang = isBinFile(filePath, p.bin, basedir)
const info = getShebangInfo(sourceCode)
return {
Program(node) {
if (needsShebang ? NODE_SHEBANG_PATTERN.test(info.shebang) : !info.shebang) {
// Good the shebang target.
// Checks BOM and \r.
if (needsShebang && info.bom) {
context.report({
node,
message: "This file must not have Unicode BOM.",
fix(fixer) {
return fixer.removeRange([-1, 0])
},
})
}
if (needsShebang && info.cr) {
context.report({
node,
message: "This file must have Unix linebreaks (LF).",
fix(fixer) {
const index = sourceCode.text.indexOf("\r")
return fixer.removeRange([index, index + 1])
},
})
}
}
else if (needsShebang) {
// Shebang is lacking.
context.report({
node,
message: "This file needs shebang \"#!/usr/bin/env node\".",
fix(fixer) {
return fixer.replaceTextRange([-1, info.length], NODE_SHEBANG)
},
})
}
else {
// Shebang is extra.
context.report({
node,
message: "This file needs no shebang.",
fix(fixer) {
return fixer.removeRange([0, info.length])
},
})
}
},
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
create,
meta: {
docs: {
description: "enforce the correct usage of shebang",
category: "Possible Errors",
recommended: true,
url: getDocsUrl("shebang.md"),
},
fixable: "code",
schema: [
{
type: "object",
properties: { //
convertPath: getConvertPath.schema,
},
additionalProperties: false,
},
],
},
}
@@ -0,0 +1,68 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const SKIP_TIME = 5000
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* The class of cache.
* The cache will dispose of each value if the value has not been accessed
* during 5 seconds.
*/
module.exports = class Cache {
/**
* Initialize this cache instance.
*/
constructor() {
this.map = new Map()
}
/**
* Get the cached value of the given key.
* @param {any} key The key to get.
* @returns {any} The cached value or null.
*/
get(key) {
const entry = this.map.get(key)
const now = Date.now()
if (entry) {
if (entry.expire > now) {
entry.expire = now + SKIP_TIME
return entry.value
}
this.map.delete(key)
}
return null
}
/**
* Set the value of the given key.
* @param {any} key The key to set.
* @param {any} value The value to set.
* @returns {void}
*/
set(key, value) {
const entry = this.map.get(key)
const expire = Date.now() + SKIP_TIME
if (entry) {
entry.value = value
entry.expire = expire
}
else {
this.map.set(key, { value, expire })
}
}
}
@@ -0,0 +1,53 @@
/**
* @fileoverview Rule to check whether or not `require()` is valid.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const exists = require("./exists")
const getAllowModules = require("./get-allow-modules")
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Checks whether or not each requirement target exists.
*
* It looks up the target according to the logic of Node.js.
* See Also: https://nodejs.org/api/modules.html
*
* @param {RuleContext} context - A context to report.
* @param {ImportTarget[]} targets - A list of target information to check.
* @returns {void}
*/
module.exports = function checkForExistence(context, targets) {
const allowed = new Set(getAllowModules(context))
for (const target of targets) {
const missingModule = (
target.moduleName != null &&
!allowed.has(target.moduleName) &&
target.filePath == null
)
const missingFile = (
target.moduleName == null &&
!exists(target.filePath)
)
if (missingModule || missingFile) {
context.report({
node: target.node,
loc: target.node.loc,
message: "\"{{name}}\" is not found.",
data: target,
})
}
}
}
@@ -0,0 +1,62 @@
/**
* @author Toru Nagashima
* @copyright 2017 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const getAllowModules = require("./get-allow-modules")
const getPackageJson = require("./get-package-json")
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Checks whether or not each requirement target is published via package.json.
*
* It reads package.json and checks the target exists in `dependencies`.
*
* @param {RuleContext} context - A context to report.
* @param {string} filePath - The current file path.
* @param {ImportTarget[]} targets - A list of target information to check.
* @returns {void}
*/
module.exports = function checkForExtraneous(context, filePath, targets) {
const packageInfo = getPackageJson(filePath)
if (!packageInfo) {
return
}
const allowed = new Set(getAllowModules(context))
const dependencies = new Set(
[].concat(
Object.keys(packageInfo.dependencies || {}),
Object.keys(packageInfo.devDependencies || {}),
Object.keys(packageInfo.peerDependencies || {}),
Object.keys(packageInfo.optionalDependencies || {})
)
)
for (const target of targets) {
const extraneous = (
target.moduleName != null &&
target.filePath != null &&
!dependencies.has(target.moduleName) &&
!allowed.has(target.moduleName)
)
if (extraneous) {
context.report({
node: target.node,
loc: target.node.loc,
message: "\"{{moduleName}}\" is extraneous.",
data: target,
})
}
}
}
@@ -0,0 +1,82 @@
/**
* @fileoverview Rule to check whether or not `require()` is valid.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const path = require("path")
const getAllowModules = require("./get-allow-modules")
const getConvertPath = require("./get-convert-path")
const getNpmignore = require("./get-npmignore")
const getPackageJson = require("./get-package-json")
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Checks whether or not each requirement target is published via package.json.
*
* It reads package.json and checks the target exists in `dependencies`.
*
* @param {RuleContext} context - A context to report.
* @param {string} filePath - The current file path.
* @param {ImportTarget[]} targets - A list of target information to check.
* @returns {void}
*/
module.exports = function checkForPublish(context, filePath, targets) {
const packageInfo = getPackageJson(filePath)
if (!packageInfo) {
return
}
const allowed = new Set(getAllowModules(context))
const convertPath = getConvertPath(context)
const basedir = path.dirname(packageInfo.filePath)
const toRelative = function(fullPath) { // eslint-disable-line func-style
const retv = path.relative(basedir, fullPath).replace(/\\/g, "/")
return convertPath(retv)
}
const npmignore = getNpmignore(filePath)
const devDependencies = new Set(
Object.keys(packageInfo.devDependencies || {})
)
const dependencies = new Set(
[].concat(
Object.keys(packageInfo.dependencies || {}),
Object.keys(packageInfo.peerDependencies || {}),
Object.keys(packageInfo.optionalDependencies || {})
)
)
if (!npmignore.match(toRelative(filePath))) {
// This file is published, so this cannot import private files.
for (const target of targets) {
const isPrivateFile = (
target.moduleName == null &&
npmignore.match(toRelative(target.filePath))
)
const isDevPackage = (
target.moduleName != null &&
devDependencies.has(target.moduleName) &&
!dependencies.has(target.moduleName) &&
!allowed.has(target.moduleName)
)
if (isPrivateFile || isDevPackage) {
context.report({
node: target.node,
loc: target.node.loc,
message: "\"{{name}}\" is not published.",
data: { name: target.moduleName || target.name },
})
}
}
}
}
@@ -0,0 +1,419 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = {
modules: {
_linklist: {
$deprecated: true,
since: 5,
replacedBy: null,
},
async_hooks: { //eslint-disable-line camelcase
currentId: {
$deprecated: true,
since: 8.2,
replacedBy: "'async_hooks.executionAsyncId()'",
},
triggerId: {
$deprecated: true,
since: 8.2,
replacedBy: "'async_hooks.triggerAsyncId()'",
},
},
buffer: {
Buffer: {
$constructor: {
$deprecated: true,
since: 6,
replacedBy: "'buffer.Buffer.alloc()' or 'buffer.Buffer.from()' (use 'https://www.npmjs.com/package/safe-buffer' for '<4.5.0')",
},
$call: {
$deprecated: true,
since: 6,
replacedBy: "'buffer.Buffer.alloc()' or 'buffer.Buffer.from()' (use 'https://www.npmjs.com/package/safe-buffer' for '<4.5.0')",
},
},
SlowBuffer: {
$deprecated: true,
since: 6,
replacedBy: "'buffer.Buffer.allocUnsafeSlow()'",
},
},
constants: {
$deprecated: true,
since: 6.3,
replacedBy: "'constants' property of each module",
},
crypto: {
Credentials: {
$deprecated: true,
since: 0.12,
replacedBy: "'tls.SecureContext'",
},
createCredentials: {
$deprecated: true,
since: 0.12,
replacedBy: "'tls.createSecureContext()'",
},
},
domain: {
$deprecated: true,
since: 4,
replacedBy: null,
},
events: {
EventEmitter: {
listenerCount: {
$deprecated: true,
since: 4,
replacedBy: "'events.EventEmitter#listenerCount()'",
},
},
listenerCount: {
$deprecated: true,
since: 4,
replacedBy: "'events.EventEmitter#listenerCount()'",
},
},
freelist: {
$deprecated: true,
since: 4,
replacedBy: null,
},
fs: {
SyncWriteStream: {
$deprecated: true,
since: 4,
replacedBy: null,
},
exists: {
$deprecated: true,
since: 4,
replacedBy: "'fs.stat()' or 'fs.access()'",
},
lchmod: {
$deprecated: true,
since: 0.4,
replacedBy: null,
},
lchmodSync: {
$deprecated: true,
since: 0.4,
replacedBy: null,
},
lchown: {
$deprecated: true,
since: 0.4,
replacedBy: null,
},
lchownSync: {
$deprecated: true,
since: 0.4,
replacedBy: null,
},
},
http: {
createClient: {
$deprecated: true,
since: 0.10,
replacedBy: "'http.request()'",
},
},
module: {
Module: {
requireRepl: {
$deprecated: true,
since: 6,
replacedBy: "'require(\"repl\")'",
},
_debug: {
$deprecated: true,
since: 9,
replacedBy: null,
},
},
requireRepl: {
$deprecated: true,
since: 6,
replacedBy: "'require(\"repl\")'",
},
_debug: {
$deprecated: true,
since: 9,
replacedBy: null,
},
},
os: {
getNetworkInterfaces: {
$deprecated: true,
since: 0.6,
replacedBy: "'os.networkInterfaces()'",
},
tmpDir: {
$deprecated: true,
since: 7,
replacedBy: "'os.tmpdir()'",
},
},
path: {
_makeLong: {
$deprecated: true,
since: 9,
replacedBy: "'path.toNamespacedPath()'",
},
},
punycode: {
$deprecated: true,
since: 7,
replacedBy: "'https://www.npmjs.com/package/punycode'",
},
readline: {
codePointAt: {
$deprecated: true,
since: 4,
replacedBy: null,
},
getStringWidth: {
$deprecated: true,
since: 6,
replacedBy: null,
},
isFullWidthCodePoint: {
$deprecated: true,
since: 6,
replacedBy: null,
},
stripVTControlCharacters: {
$deprecated: true,
since: 6,
replacedBy: null,
},
},
sys: {
$deprecated: true,
since: 0.3,
replacedBy: "'util' module",
},
tls: {
CleartextStream: {
$deprecated: true,
since: 0.10,
replacedBy: null,
},
CryptoStream: {
$deprecated: true,
since: 0.12,
replacedBy: "'tls.TLSSocket'",
},
SecurePair: {
$deprecated: true,
since: 6,
replacedBy: "'tls.TLSSocket'",
},
createSecurePair: {
$deprecated: true,
since: 6,
replacedBy: "'tls.TLSSocket'",
},
parseCertString: {
$deprecated: true,
since: 8.6,
replacedBy: "'querystring.parse()'",
},
},
tty: {
setRawMode: {
$deprecated: true,
since: 0.10,
replacedBy: "'tty.ReadStream#setRawMode()' (e.g. 'process.stdin.setRawMode()')",
},
},
util: {
debug: {
$deprecated: true,
since: 0.12,
replacedBy: "'console.error()'",
},
error: {
$deprecated: true,
since: 0.12,
replacedBy: "'console.error()'",
},
isArray: {
$deprecated: true,
since: 4,
replacedBy: "'Array.isArray()'",
},
isBoolean: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isBuffer: {
$deprecated: true,
since: 4,
replacedBy: "'Buffer.isBuffer()'",
},
isDate: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isError: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isFunction: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isNull: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isNullOrUndefined: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isNumber: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isObject: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isPrimitive: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isRegExp: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isString: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isSymbol: {
$deprecated: true,
since: 4,
replacedBy: null,
},
isUndefined: {
$deprecated: true,
since: 4,
replacedBy: null,
},
log: {
$deprecated: true,
since: 6,
replacedBy: "a third party module",
},
print: {
$deprecated: true,
since: 0.12,
replacedBy: "'console.log()'",
},
pump: {
$deprecated: true,
since: 0.10,
replacedBy: "'stream.Readable#pipe()'",
},
puts: {
$deprecated: true,
since: 0.12,
replacedBy: "'console.log()'",
},
_extend: {
$deprecated: true,
since: 6,
replacedBy: "'Object.assign()'",
},
},
vm: {
runInDebugContext: {
$deprecated: true,
since: 8,
replacedBy: null,
},
},
},
globals: {
Buffer: {
$constructor: {
$deprecated: true,
global: true,
since: 6,
replacedBy: "'Buffer.alloc()' or 'Buffer.from()' (use 'https://www.npmjs.com/package/safe-buffer' for '<4.5.0')",
},
$call: {
$deprecated: true,
global: true,
since: 6,
replacedBy: "'Buffer.alloc()' or 'Buffer.from()' (use 'https://www.npmjs.com/package/safe-buffer' for '<4.5.0')",
},
},
GLOBAL: {
$deprecated: true,
global: true,
since: 6,
replacedBy: "'global'",
},
Intl: {
v8BreakIterator: {
$deprecated: true,
global: true,
since: 7,
replacedBy: null,
},
},
require: {
extensions: {
$deprecated: true,
global: true,
since: 0.12,
replacedBy: "compiling them ahead of time",
},
},
root: {
$deprecated: true,
global: true,
since: 6,
replacedBy: "'global'",
},
process: {
EventEmitter: {
$deprecated: true,
global: true,
since: 0.6,
replacedBy: "'require(\"events\")'",
},
env: {
NODE_REPL_HISTORY_FILE: {
$deprecated: true,
global: true,
since: 4,
replacedBy: "'NODE_REPL_HISTORY'",
},
},
},
},
}
@@ -0,0 +1,73 @@
/**
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const fs = require("fs")
const path = require("path")
const Cache = require("./cache")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const ROOT = /^(?:[/.]|\.\.|[A-Z]:\\|\\\\)(?:[/\\]\.\.)*$/
const cache = new Cache()
/**
* Check whether the file exists or not.
* @param {string} filePath The file path to check.
* @returns {boolean} `true` if the file exists.
*/
function existsCaseSensitive(filePath) {
let dirPath = filePath
while (dirPath !== "" && !ROOT.test(dirPath)) {
const fileName = path.basename(dirPath)
dirPath = path.dirname(dirPath)
if (fs.readdirSync(dirPath).indexOf(fileName) === -1) {
return false
}
}
return true
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Checks whether or not the file of a given path exists.
*
* @param {string} filePath - A file path to check.
* @returns {boolean} `true` if the file of a given path exists.
*/
module.exports = function exists(filePath) {
let result = cache.get(filePath)
if (result == null) {
try {
const relativePath = path.relative(process.cwd(), filePath)
result = (
fs.statSync(relativePath).isFile() &&
existsCaseSensitive(relativePath)
)
}
catch (error) {
if (error.code !== "ENOENT") {
throw error
}
result = false
}
cache.set(filePath, result)
}
return result
}
@@ -0,0 +1,736 @@
/**
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = {
//--------------------------------------------------------------------------
// Syntax
//--------------------------------------------------------------------------
"defaultParameters": {
alias: ["syntax"],
name: "Default parameters",
node: "6.0.0",
},
"restParameters": {
alias: ["syntax"],
name: "Rest parameters",
node: "6.0.0",
},
"spreadOperators": {
alias: ["syntax"],
name: "Spread operators",
node: "5.0.0",
},
"objectLiteralExtensions": {
alias: ["syntax"],
name: "Object literal extensions",
node: "4.0.0",
},
"objectPropertyShorthandOfGetSet": {
alias: ["syntax", "objectLiteralExtensions"],
name: "Property shorthand of 'get' and 'set'",
node: "6.0.0",
},
"forOf": {
alias: ["syntax"],
name: "'for..of' loops",
node: "0.12.0",
},
"binaryNumberLiterals": {
alias: ["syntax"],
name: "Binary number literals",
node: "4.0.0",
},
"octalNumberLiterals": {
alias: ["syntax"],
name: "Octal number literals",
node: "4.0.0",
},
"templateStrings": {
alias: ["syntax"],
name: "Template strings",
node: "4.0.0",
},
"regexpY": {
alias: ["syntax"],
name: "RegExp 'y' flags",
node: "6.0.0",
},
"regexpU": {
alias: ["syntax"],
name: "RegExp 'u' flags",
node: "6.0.0",
},
"destructuring": {
alias: ["syntax"],
name: "Destructuring",
node: "6.0.0",
},
"unicodeCodePointEscapes": {
alias: ["syntax"],
name: "Unicode code point escapes",
node: "4.0.0",
},
"new.target": {
alias: ["syntax"],
name: "'new.target'",
node: "5.0.0",
},
"const": {
alias: ["syntax"],
name: "'const' declarations",
node: {
sloppy: "6.0.0",
strict: "4.0.0",
},
},
"let": {
alias: ["syntax"],
name: "'let' declarations",
node: {
sloppy: "6.0.0",
strict: "4.0.0",
},
},
"blockScopedFunctions": {
alias: ["syntax"],
name: "Block-scoped functions",
node: {
sloppy: "6.0.0",
strict: "4.0.0",
},
},
"arrowFunctions": {
alias: ["syntax"],
name: "Arrow functions",
node: "4.0.0",
},
"generatorFunctions": {
alias: ["syntax"],
name: "Generator functions",
node: "4.0.0",
},
"classes": {
alias: ["syntax"],
name: "Classes",
node: {
sloppy: "6.0.0",
strict: "4.0.0",
},
},
"modules": {
alias: ["syntax"],
name: "Import and export declarations",
node: null,
},
"exponentialOperators": {
alias: ["syntax"],
name: "Exponential operators (**)",
node: "7.0.0",
},
"asyncAwait": {
alias: ["syntax"],
name: "Async functions",
node: "7.6.0",
},
"trailingCommasInFunctions": {
alias: ["syntax"],
name: "Trailing commas in functions",
node: "8.0.0",
},
//------------------------------------------
"templateLiteralRevision": {
alias: ["syntax"],
name: "Illegal escape sequences in taggled templates",
node: "9.0.0",
},
"regexpS": {
alias: ["syntax"],
name: "RegExp 's' flags",
node: "9.0.0",
},
"regexpNamedCaptureGroups": {
alias: ["syntax"],
name: "RegExp named capture groups",
node: "10.0.0",
},
"regexpLookbehind": {
alias: ["syntax"],
name: "RegExp lookbehind assertions",
node: "9.0.0",
},
"regexpUnicodeProperties": {
alias: ["syntax"],
name: "RegExp Unicode property escapes",
node: "10.0.0",
},
"restProperties": {
alias: ["syntax"],
name: "Rest properties",
node: "8.3.0",
},
"spreadProperties": {
alias: ["syntax"],
name: "Spread properties",
node: "8.3.0",
},
"asyncGenerators": {
alias: ["syntax"],
name: "Async generators",
node: "10.0.0",
},
"forAwaitOf": {
alias: ["syntax"],
name: "for-await-of loops",
node: "10.0.0",
},
//--------------------------------------------------------------------------
// Runtime
//--------------------------------------------------------------------------
"Int8Array": {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Int8Array'",
singular: true,
node: "0.12.0",
},
"Uint8Array": {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Uint8Array'",
singular: true,
node: "0.12.0",
},
"Uint8ClampedArray": {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Uint8ClampedArray'",
singular: true,
node: "0.12.0",
},
"Int16Array": {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Int16Array'",
singular: true,
node: "0.12.0",
},
"Uint16Array": {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Uint16Array'",
singular: true,
node: "0.12.0",
},
"Int32Array": {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Int32Array'",
singular: true,
node: "0.12.0",
},
"Uint32Array": {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Uint32Array'",
singular: true,
node: "0.12.0",
},
"Float32Array": {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Float32Array'",
singular: true,
node: "0.12.0",
},
"Float64Array": {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'Float64Array'",
singular: true,
node: "0.12.0",
},
"DataView": {
alias: ["runtime", "globalObjects", "typedArrays"],
name: "'DataView'",
singular: true,
node: "0.12.0",
},
"Map": {
alias: ["runtime", "globalObjects"],
name: "'Map'",
singular: true,
node: "0.12.0",
},
"Set": {
alias: ["runtime", "globalObjects"],
name: "'Set'",
singular: true,
node: "0.12.0",
},
"WeakMap": {
alias: ["runtime", "globalObjects"],
name: "'WeakMap'",
singular: true,
node: "0.12.0",
},
"WeakSet": {
alias: ["runtime", "globalObjects"],
name: "'WeakSet'",
singular: true,
node: "0.12.0",
},
"Proxy": {
alias: ["runtime", "globalObjects"],
name: "'Proxy'",
singular: true,
node: "6.0.0",
},
"Reflect": {
alias: ["runtime", "globalObjects"],
name: "'Reflect'",
singular: true,
node: "6.0.0",
},
"Promise": {
alias: ["runtime", "globalObjects"],
name: "'Promise'",
singular: true,
node: "0.12.0",
},
"Symbol": {
alias: ["runtime", "globalObjects"],
name: "'Symbol'",
singular: true,
node: "0.12.0",
},
"SharedArrayBuffer": {
alias: ["runtime", "globalObjects"],
name: "'SharedArrayBuffer'",
singular: true,
node: "9.0.0",
},
"Atomics": {
alias: ["runtime", "globalObjects"],
name: "'Atomics'",
singular: true,
node: "9.0.0",
},
"Object.assign": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.assign'",
singular: true,
node: "4.0.0",
},
"Object.is": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.is'",
singular: true,
node: "0.12.0",
},
"Object.getOwnPropertySymbols": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.getOwnPropertySymbols'",
singular: true,
node: "0.12.0",
},
"Object.setPrototypeOf": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.setPrototypeOf'",
singular: true,
node: "0.12.0",
},
"Object.values": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.values'",
singular: true,
node: "7.0.0",
},
"Object.entries": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.entries'",
singular: true,
node: "7.0.0",
},
"Object.getOwnPropertyDescriptors": {
alias: ["runtime", "staticMethods", "Object.*"],
name: "'Object.getOwnPropertyDescriptors'",
singular: true,
node: "7.0.0",
},
"String.raw": {
alias: ["runtime", "staticMethods", "String.*"],
name: "'String.raw'",
singular: true,
node: "4.0.0",
},
"String.fromCodePoint": {
alias: ["runtime", "staticMethods", "String.*"],
name: "'String.fromCodePoint'",
singular: true,
node: "4.0.0",
},
"Array.from": {
alias: ["runtime", "staticMethods", "Array.*"],
name: "'Array.from'",
singular: true,
node: "4.0.0",
},
"Array.of": {
alias: ["runtime", "staticMethods", "Array.*"],
name: "'Array.of'",
singular: true,
node: "4.0.0",
},
"Number.isFinite": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.isFinite'",
singular: true,
node: "0.10.0",
},
"Number.isInteger": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.isInteger'",
singular: true,
node: "0.12.0",
},
"Number.isSafeInteger": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.isSafeInteger'",
singular: true,
node: "0.12.0",
},
"Number.isNaN": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.isNaN'",
singular: true,
node: "0.10.0",
},
"Number.EPSILON": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.EPSILON'",
singular: true,
node: "0.12.0",
},
"Number.MIN_SAFE_INTEGER": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.MIN_SAFE_INTEGER'",
singular: true,
node: "0.12.0",
},
"Number.MAX_SAFE_INTEGER": {
alias: ["runtime", "staticMethods", "Number.*"],
name: "'Number.MAX_SAFE_INTEGER'",
singular: true,
node: "0.12.0",
},
"Math.clz32": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.clz32'",
singular: true,
node: "0.12.0",
},
"Math.imul": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.imul'",
singular: true,
node: "0.12.0",
},
"Math.sign": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.sign'",
singular: true,
node: "0.12.0",
},
"Math.log10": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.log10'",
singular: true,
node: "0.12.0",
},
"Math.log2": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.log2'",
singular: true,
node: "0.12.0",
},
"Math.log1p": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.log1p'",
singular: true,
node: "0.12.0",
},
"Math.expm1": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.expm1'",
singular: true,
node: "0.12.0",
},
"Math.cosh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.cosh'",
singular: true,
node: "0.12.0",
},
"Math.sinh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.sinh'",
singular: true,
node: "0.12.0",
},
"Math.tanh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.tanh'",
singular: true,
node: "0.12.0",
},
"Math.acosh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.acosh'",
singular: true,
node: "0.12.0",
},
"Math.asinh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.asinh'",
singular: true,
node: "0.12.0",
},
"Math.atanh": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.atanh'",
singular: true,
node: "0.12.0",
},
"Math.trunc": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.trunc'",
singular: true,
node: "0.12.0",
},
"Math.fround": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.fround'",
singular: true,
node: "0.12.0",
},
"Math.cbrt": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.cbrt'",
singular: true,
node: "0.12.0",
},
"Math.hypot": {
alias: ["runtime", "staticMethods", "Math.*"],
name: "'Math.hypot'",
singular: true,
node: "0.12.0",
},
"Symbol.hasInstance": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.hasInstance'",
singular: true,
node: "6.5.0",
},
"Symbol.isConcatSpreadablec": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.isConcatSpreadablec'",
singular: true,
node: "6.0.0",
},
"Symbol.iterator": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.iterator'",
singular: true,
node: "0.12.0",
},
"Symbol.species": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.species'",
singular: true,
node: "6.5.0",
},
"Symbol.replace": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.replace'",
singular: true,
node: "6.0.0",
},
"Symbol.search": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.search'",
singular: true,
node: "6.0.0",
},
"Symbol.split": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.split'",
singular: true,
node: "6.0.0",
},
"Symbol.match": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.match'",
singular: true,
node: "6.0.0",
},
"Symbol.toPrimitive": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.toPrimitive'",
singular: true,
node: "6.0.0",
},
"Symbol.toStringTag": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.toStringTag'",
singular: true,
node: "6.0.0",
},
"Symbol.unscopables": {
alias: ["runtime", "staticMethods", "Symbol.*"],
name: "'Symbol.unscopables'",
singular: true,
node: "4.0.0",
},
"Atomics.add": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.add'",
singular: true,
node: "9.0.0",
},
"Atomics.and": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.and'",
singular: true,
node: "9.0.0",
},
"Atomics.compareExchange": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.compareExchange'",
singular: true,
node: "9.0.0",
},
"Atomics.exchange": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.exchange'",
singular: true,
node: "9.0.0",
},
"Atomics.wait": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.wait'",
singular: true,
node: "9.0.0",
},
"Atomics.wake": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.wake'",
singular: true,
node: "9.0.0",
},
"Atomics.isLockFree": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.isLockFree'",
singular: true,
node: "9.0.0",
},
"Atomics.load": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.load'",
singular: true,
node: "9.0.0",
},
"Atomics.or": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.or'",
singular: true,
node: "9.0.0",
},
"Atomics.store": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.store'",
singular: true,
node: "9.0.0",
},
"Atomics.sub": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.sub'",
singular: true,
node: "9.0.0",
},
"Atomics.xor": {
alias: ["runtime", "staticMethods", "Atomics.*"],
name: "'Atomics.xor'",
singular: true,
node: "9.0.0",
},
"extendsArray": {
alias: ["runtime", "extends"],
name: "Subclassing of 'Array'",
singular: true,
node: "6.0.0",
},
"extendsRegExp": {
alias: ["runtime", "extends"],
name: "Subclassing of 'RegExp'",
singular: true,
node: "5.0.0",
},
"extendsFunction": {
alias: ["runtime", "extends"],
name: "Subclassing of 'Function'",
singular: true,
node: "6.0.0",
},
"extendsPromise": {
alias: ["runtime", "extends"],
name: "Subclassing of 'Promise'",
singular: true,
node: "5.0.0",
},
"extendsBoolean": {
alias: ["runtime", "extends"],
name: "Subclassing of 'Boolean'",
singular: true,
node: "4.0.0",
},
"extendsNumber": {
alias: ["runtime", "extends"],
name: "Subclassing of 'Number'",
singular: true,
node: "4.0.0",
},
"extendsString": {
alias: ["runtime", "extends"],
name: "Subclassing of 'String'",
singular: true,
node: "4.0.0",
},
"extendsMap": {
alias: ["runtime", "extends"],
name: "Subclassing of 'Map'",
singular: true,
node: "4.0.0",
},
"extendsSet": {
alias: ["runtime", "extends"],
name: "Subclassing of 'Set'",
singular: true,
node: "4.0.0",
},
"extendsNull": {
alias: ["runtime", "extends"],
name: "'extends null'",
singular: true,
node: null,
},
}
@@ -0,0 +1,56 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const DEFAULT_VALUE = Object.freeze([])
/**
* Gets `allowModules` property from a given option object.
*
* @param {object|undefined} option - An option object to get.
* @returns {string[]|null} The `allowModules` value, or `null`.
*/
function get(option) {
if (option && option.allowModules && Array.isArray(option.allowModules)) {
return option.allowModules.map(String)
}
return null
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Gets "allowModules" setting.
*
* 1. This checks `options` property, then returns it if exists.
* 2. This checks `settings.node` property, then returns it if exists.
* 3. This returns `[]`.
*
* @param {RuleContext} context - The rule context.
* @returns {string[]} A list of extensions.
*/
module.exports = function getAllowModules(context) {
return (
get(context.options && context.options[0]) ||
get(context.settings && context.settings.node) ||
DEFAULT_VALUE
)
}
module.exports.schema = {
type: "array",
items: {
type: "string",
pattern: "^(?:@[a-zA-Z0-9_\\-.]+/)?[a-zA-Z0-9_\\-.]+$",
},
uniqueItems: true,
}
@@ -0,0 +1,202 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const Minimatch = require("minimatch").Minimatch
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* @param {any} x - An any value.
* @returns {any} Always `x`.
*/
function identity(x) {
return x
}
/**
* Converts old-style value to new-style value.
*
* @param {any} x - The value to convert.
* @returns {({include: string[], exclude: string[], replace: string[]})[]} Normalized value.
*/
function normalizeValue(x) {
if (Array.isArray(x)) {
return x
}
return Object.keys(x).map(pattern => ({
include: [pattern],
exclude: [],
replace: x[pattern],
}))
}
/**
* Ensures the given value is a string array.
*
* @param {any} x - The value to ensure.
* @returns {string[]} The string array.
*/
function toStringArray(x) {
if (Array.isArray(x)) {
return x.map(String)
}
return []
}
/**
* Creates the function which checks whether a file path is matched with the given pattern or not.
*
* @param {string[]} includePatterns - The glob patterns to include files.
* @param {string[]} excludePatterns - The glob patterns to exclude files.
* @returns {function} Created predicate function.
*/
function createMatch(includePatterns, excludePatterns) {
const include = includePatterns.map(pattern => new Minimatch(pattern))
const exclude = excludePatterns.map(pattern => new Minimatch(pattern))
return (filePath) =>
include.some(m => m.match(filePath)) &&
!exclude.some(m => m.match(filePath))
}
/**
* Creates a function which replaces a given path.
*
* @param {RegExp} fromRegexp - A `RegExp` object to replace.
* @param {string} toStr - A new string to replace.
* @returns {function} A function which replaces a given path.
*/
function defineConvert(fromRegexp, toStr) {
return (filePath) =>
filePath.replace(fromRegexp, toStr)
}
/**
* Combines given converters.
* The result function converts a given path with the first matched converter.
*
* @param {{match: function, convert: function}} converters - A list of converters to combine.
* @returns {function} A function which replaces a given path.
*/
function combine(converters) {
return (filePath) => {
for (const converter of converters) {
if (converter.match(filePath)) {
return converter.convert(filePath)
}
}
return filePath
}
}
/**
* Parses `convertPath` property from a given option object.
*
* @param {object|undefined} option - An option object to get.
* @returns {function|null} A function which converts a path., or `null`.
*/
function parse(option) {
if (!option ||
!option.convertPath ||
typeof option.convertPath !== "object"
) {
return null
}
const converters = []
for (const pattern of normalizeValue(option.convertPath)) {
const include = toStringArray(pattern.include)
const exclude = toStringArray(pattern.exclude)
const fromRegexp = new RegExp(String(pattern.replace[0]))
const toStr = String(pattern.replace[1])
converters.push({
match: createMatch(include, exclude),
convert: defineConvert(fromRegexp, toStr),
})
}
return combine(converters)
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Gets "convertPath" setting.
*
* 1. This checks `options` property, then returns it if exists.
* 2. This checks `settings.node` property, then returns it if exists.
* 3. This returns a function of identity.
*
* @param {RuleContext} context - The rule context.
* @returns {function} A function which converts a path.
*/
module.exports = function getConvertPath(context) {
return (
parse(context.options && context.options[0]) ||
parse(context.settings && context.settings.node) ||
identity
)
}
/**
* JSON Schema for `convertPath` option.
*/
module.exports.schema = {
anyOf: [
{
type: "object",
properties: {},
patternProperties: {
"^.+$": {
type: "array",
items: { type: "string" },
minItems: 2,
maxItems: 2,
},
},
additionalProperties: false,
},
{
type: "array",
items: {
type: "object",
properties: {
include: {
type: "array",
items: { type: "string" },
minItems: 1,
uniqueItems: true,
},
exclude: {
type: "array",
items: { type: "string" },
uniqueItems: true,
},
replace: {
type: "array",
items: { type: "string" },
minItems: 2,
maxItems: 2,
},
},
additionalProperties: false,
required: ["include", "replace"],
},
minItems: 1,
},
],
}
@@ -0,0 +1,34 @@
/**
* @author Suhas Karanth
* @copyright 2018 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const pkg = require("../../package")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const REPO_URL = "https://github.com/mysticatea/eslint-plugin-node"
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Generates the URL to documentation for the given rule name. It uses the
* package version to build the link to a tagged version of the
* documentation file.
*
* @param {string} ruleName - Name of the eslint rule
* @returns {string} URL to the documentation for the given rule
*/
module.exports = function getDocsUrl(ruleName) {
return `${REPO_URL}/blob/v${pkg.version}/docs/rules/${ruleName}.md`
}
@@ -0,0 +1,61 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const path = require("path")
const resolve = require("resolve")
const getResolvePaths = require("./get-resolve-paths")
const getTryExtensions = require("./get-try-extensions")
const ImportTarget = require("./import-target")
const stripImportPathParams = require("./strip-import-path-params")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const MODULE_TYPE = /^(?:Import|Export(?:Named|Default|All))Declaration$/
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/**
* Gets a list of `import`/`export` declaration targets.
*
* Core modules of Node.js (e.g. `fs`, `http`) are excluded.
*
* @param {RuleContext} context - The rule context.
* @param {ASTNode} programNode - The node of Program.
* @param {boolean} includeCore - The flag to include core modules.
* @returns {ImportTarget[]} A list of found target's information.
*/
module.exports = function getImportExportTargets(context, programNode, includeCore) {
const retv = []
const basedir = path.dirname(path.resolve(context.getFilename()))
const paths = getResolvePaths(context)
const extensions = getTryExtensions(context)
const options = { basedir, paths, extensions }
for (const statement of programNode.body) {
// Skip if it's not a module declaration.
if (!MODULE_TYPE.test(statement.type)) {
continue
}
// Gets the target module.
const node = statement.source
const name = node && stripImportPathParams(node.value)
if (name && (includeCore || !resolve.isCore(name))) {
retv.push(new ImportTarget(node, name, options))
}
}
return retv
}
@@ -0,0 +1,179 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const fs = require("fs")
const path = require("path")
const ignore = require("ignore")
const Cache = require("./cache")
const exists = require("./exists")
const getPackageJson = require("./get-package-json")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const cache = new Cache()
const SLASH_AT_BEGIN_AND_END = /^!?\/+|^!|\/+$/g
const PARENT_RELATIVE_PATH = /^\.\./
const NEVER_IGNORED = /^(?:readme\.[^.]*|(?:licen[cs]e|changes|changelog|history)(?:\.[^.]*)?)$/i
/**
* Checks whether or not a given file name is a relative path to a ancestor
* directory.
*
* @param {string} filePath - A file name to check.
* @returns {boolean} `true` if the file name is a relative path to a ancestor
* directory.
*/
function notAncestorFiles(filePath) {
return PARENT_RELATIVE_PATH.test(filePath)
}
/**
* @param {function} f - A function.
* @param {function} g - A function.
* @returns {function} A logical-and function of `f` and `g`.
*/
function and(f, g) {
return (filePath) => f(filePath) && g(filePath)
}
/**
* @param {function} f - A function.
* @param {function} g - A function.
* @param {function|null} h - A function.
* @returns {function} A logical-or function of `f`, `g`, and `h`.
*/
function or(f, g, h) {
return (filePath) => f(filePath) || g(filePath) || (h && h(filePath))
}
/**
* @param {function} f - A function.
* @returns {function} A logical-not function of `f`.
*/
function not(f) {
return (filePath) => !f(filePath)
}
/**
* Creates a function which checks whether or not a given file is ignoreable.
*
* @param {object} p - An object of package.json.
* @returns {function} A function which checks whether or not a given file is ignoreable.
*/
function filterNeverIgnoredFiles(p) {
const basedir = path.dirname(p.filePath)
const mainFilePath = (typeof p.main === "string") ? path.join(basedir, p.main) : null
return (filePath) => (
path.join(basedir, filePath) !== mainFilePath &&
filePath !== "package.json" &&
!NEVER_IGNORED.test(path.relative(basedir, filePath))
)
}
/**
* Creates a function which checks whether or not a given file should be ignored.
*
* @param {string[]|null} files - File names of whitelist.
* @returns {function|null} A function which checks whether or not a given file should be ignored.
*/
function parseWhiteList(files) {
if (!files || !Array.isArray(files)) {
return null
}
const ig = ignore()
ig.add("*")
for (const file of files) {
if (typeof file === "string" && file) {
const prefix = file.startsWith("!") ? "" : "!"
const body = file.replace(SLASH_AT_BEGIN_AND_END, "")
ig.add(`${prefix}/${body}`)
ig.add(`${prefix}/${body}/**`)
}
}
return not(ig.createFilter())
}
/**
* Creates a function which checks whether or not a given file should be ignored.
*
* @param {string} basedir - The directory path "package.json" exists.
* @param {boolean} filesFieldExists - `true` if `files` field of `package.json` exists.
* @returns {function|null} A function which checks whether or not a given file should be ignored.
*/
function parseNpmignore(basedir, filesFieldExists) {
let filePath = path.join(basedir, ".npmignore")
if (!exists(filePath)) {
if (filesFieldExists) {
return null
}
filePath = path.join(basedir, ".gitignore")
if (!exists(filePath)) {
return null
}
}
const ig = ignore()
ig.add(fs.readFileSync(filePath, "utf8"))
return not(ig.createFilter())
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Gets an object to check whether or not a given path should be ignored.
* The object is created from:
*
* - `files` field of `package.json`
* - `.npmignore`
*
* @param {string} startPath - A file path to lookup.
* @returns {object}
* An object to check whther or not a given path should be ignored.
* The object has a method `match`.
* `match` returns `true` if a given file path should be ignored.
*/
module.exports = function getNpmignore(startPath) {
const retv = { match: notAncestorFiles }
const p = getPackageJson(startPath)
if (p) {
const data = cache.get(p.filePath)
if (data) {
return data
}
const filesIgnore = parseWhiteList(p.files)
const npmignoreIgnore = parseNpmignore(path.dirname(p.filePath), Boolean(filesIgnore))
if (filesIgnore && npmignoreIgnore) {
retv.match = and(filterNeverIgnoredFiles(p), or(notAncestorFiles, filesIgnore, npmignoreIgnore))
}
else if (filesIgnore) {
retv.match = and(filterNeverIgnoredFiles(p), or(notAncestorFiles, filesIgnore))
}
else if (npmignoreIgnore) {
retv.match = and(filterNeverIgnoredFiles(p), or(notAncestorFiles, npmignoreIgnore))
}
cache.set(p.filePath, retv)
}
return retv
}
@@ -0,0 +1,90 @@
/**
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const fs = require("fs")
const path = require("path")
const Cache = require("./cache")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const cache = new Cache()
/**
* Reads the `package.json` data in a given path.
*
* Don't cache the data.
*
* @param {string} dir - The path to a directory to read.
* @returns {object|null} The read `package.json` data, or null.
*/
function readPackageJson(dir) {
const filePath = path.join(dir, "package.json")
try {
const text = fs.readFileSync(filePath, "utf8")
const data = JSON.parse(text)
if (typeof data === "object" && data !== null) {
data.filePath = filePath
return data
}
}
catch (_err) {
// do nothing.
}
return null
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Gets a `package.json` data.
* The data is cached if found, then it's used after.
*
* @param {string} startPath - A file path to lookup.
* @returns {object|null} A found `package.json` data or `null`.
* This object have additional property `filePath`.
*/
module.exports = function getPackageJson(startPath) {
const startDir = path.dirname(path.resolve(startPath))
let dir = startDir
let prevDir = ""
let data = null
do {
data = cache.get(dir)
if (data) {
if (dir !== startDir) {
cache.set(startDir, data)
}
return data
}
data = readPackageJson(dir)
if (data) {
cache.set(dir, data)
cache.set(startDir, data)
return data
}
// Go to next.
prevDir = dir
dir = path.resolve(dir, "..")
}
while (dir !== prevDir)
cache.set(startDir, null)
return null
}
@@ -0,0 +1,88 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const path = require("path")
const resolve = require("resolve")
const getResolvePaths = require("./get-resolve-paths")
const getTryExtensions = require("./get-try-extensions")
const getValueIfString = require("./get-value-if-string")
const ImportTarget = require("./import-target")
const stripImportPathParams = require("./strip-import-path-params")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Checks whether or not a given node is a callee.
*
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is a callee.
*/
function isCallee(node) {
return node.parent.type === "CallExpression" && node.parent.callee === node
}
/**
* Gets references of "require".
*
* @param {escope.Scope} scope - The global scope.
* @returns {escope.Reference[]} References of "require".
*/
function getReferencesOfRequire(scope) {
const variable = scope.set.get("require")
if (!variable) {
// Not found.
return []
}
return variable.references
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Gets a list of `require()` targets.
*
* Core modules of Node.js (e.g. `fs`, `http`) are excluded.
*
* @param {RuleContext} context - The rule context.
* @param {boolean} includeCore - The flag to include core modules.
* @returns {ImportTarget[]} A list of found target's information.
*/
module.exports = function getRequireTargets(context, includeCore) {
const retv = []
const basedir = path.dirname(path.resolve(context.getFilename()))
const paths = getResolvePaths(context)
const references = getReferencesOfRequire(context.getScope())
const extensions = getTryExtensions(context)
const options = { basedir, paths, extensions }
for (const reference of references) {
const node = reference.identifier
// Skips if it's not a call of `require`.
if (!isCallee(node)) {
continue
}
// Gets the target module.
const targetNode = node.parent.arguments[0]
const rawName = getValueIfString(targetNode)
const name = rawName && stripImportPathParams(rawName)
if (name && (includeCore || !resolve.isCore(name))) {
retv.push(new ImportTarget(targetNode, name, options))
}
}
return retv
}
@@ -0,0 +1,53 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const DEFAULT_VALUE = Object.freeze([])
/**
* Gets `resolvePaths` property from a given option object.
*
* @param {object|undefined} option - An option object to get.
* @returns {string[]|null} The `allowModules` value, or `null`.
*/
function get(option) {
if (option && option.resolvePaths && Array.isArray(option.resolvePaths)) {
return option.resolvePaths.map(String)
}
return null
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Gets "resolvePaths" setting.
*
* 1. This checks `options` property, then returns it if exists.
* 2. This checks `settings.node` property, then returns it if exists.
* 3. This returns `[]`.
*
* @param {RuleContext} context - The rule context.
* @returns {string[]} A list of extensions.
*/
module.exports = function getResolvePaths(context) {
return (
get(context.options && context.options[0]) ||
get(context.settings && context.settings.node) ||
DEFAULT_VALUE
)
}
module.exports.schema = {
type: "array",
items: { type: "string" },
uniqueItems: true,
}
@@ -0,0 +1,56 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const DEFAULT_VALUE = Object.freeze([".js", ".json", ".node"])
/**
* Gets `tryExtensions` property from a given option object.
*
* @param {object|undefined} option - An option object to get.
* @returns {string[]|null} The `tryExtensions` value, or `null`.
*/
function get(option) {
if (option && option.tryExtensions && Array.isArray(option.tryExtensions)) {
return option.tryExtensions.map(String)
}
return null
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Gets "tryExtensions" setting.
*
* 1. This checks `options` property, then returns it if exists.
* 2. This checks `settings.node` property, then returns it if exists.
* 3. This returns `[".js", ".json", ".node"]`.
*
* @param {RuleContext} context - The rule context.
* @returns {string[]} A list of extensions.
*/
module.exports = function getTryExtensions(context) {
return (
get(context.options && context.options[0]) ||
get(context.settings && context.settings.node) ||
DEFAULT_VALUE
)
}
module.exports.schema = {
type: "array",
items: {
type: "string",
pattern: "^\\.",
},
uniqueItems: true,
}
@@ -0,0 +1,40 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Gets the value of a given node if it's a literal or a template literal.
*
* @param {ASTNode} node - A node to get.
* @returns {string|null} The value of the node, or `null`.
*/
module.exports = function getValueIfString(node) {
if (!node) {
return null
}
switch (node.type) {
case "Literal":
if (typeof node.value === "string") {
return node.value
}
break
case "TemplateLiteral":
if (node.expressions.length === 0) {
return node.quasis[0].value.cooked
}
break
// no default
}
return null
}
@@ -0,0 +1,99 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const path = require("path")
const resolve = require("resolve")
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Resolve the given id to file paths.
* @param {boolean} isModule The flag which indicates this id is a module.
* @param {string} id The id to resolve.
* @param {object} options The options of node-resolve module.
* It requires `options.basedir`.
* @returns {string|null} The resolved path.
*/
function getFilePath(isModule, id, options) {
try {
return resolve.sync(id, options)
}
catch (_err) {
if (isModule) {
return null
}
return path.resolve(options.basedir, id)
}
}
/**
* Gets the module name of a given path.
*
* e.g. `eslint/lib/ast-utils` -> `eslint`
*
* @param {string} nameOrPath - A path to get.
* @returns {string} The module name of the path.
*/
function getModuleName(nameOrPath) {
let end = nameOrPath.indexOf("/")
if (end !== -1 && nameOrPath[0] === "@") {
end = nameOrPath.indexOf("/", 1 + end)
}
return end === -1 ? nameOrPath : nameOrPath.slice(0, end)
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Information of an import target.
*/
module.exports = class ImportTarget {
/**
* Initialize this instance.
* @param {ASTNode} node - The node of a `require()` or a module declaraiton.
* @param {string} name - The name of an import target.
* @param {object} options - The options of `node-resolve` module.
*/
constructor(node, name, options) {
const isModule = !/^(?:[./\\]|\w+:)/.test(name)
/**
* The node of a `require()` or a module declaraiton.
* @type {ASTNode}
*/
this.node = node
/**
* The name of this import target.
* @type {string}
*/
this.name = name
/**
* The full path of this import target.
* If the target is a module and it does not exist then this is `null`.
* @type {string|null}
*/
this.filePath = getFilePath(isModule, name, options)
/**
* The module name of this import target.
* If the target is a relative path then this is `null`.
* @type {string|null}
*/
this.moduleName = isModule ? getModuleName(name) : null
}
}
@@ -0,0 +1,15 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
module.exports = function stripImportPathParams(path) {
const i = path.indexOf("!")
return i === -1 ? path : path.slice(0, i)
}
@@ -0,0 +1,94 @@
{
"_from": "eslint-plugin-node@^6.0.1",
"_id": "eslint-plugin-node@6.0.1",
"_inBundle": false,
"_integrity": "sha512-Q/Cc2sW1OAISDS+Ji6lZS2KV4b7ueA/WydVWd1BECTQwVvfQy5JAi3glhINoKzoMnfnuRgNP+ZWKrGAbp3QDxw==",
"_location": "/@vue/eslint-config-standard/eslint-plugin-node",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "eslint-plugin-node@^6.0.1",
"name": "eslint-plugin-node",
"escapedName": "eslint-plugin-node",
"rawSpec": "^6.0.1",
"saveSpec": null,
"fetchSpec": "^6.0.1"
},
"_requiredBy": [
"/@vue/eslint-config-standard"
],
"_resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-6.0.1.tgz",
"_shasum": "bf19642298064379315d7a4b2a75937376fa05e4",
"_spec": "eslint-plugin-node@^6.0.1",
"_where": "D:\\Atom\\Git\\Laravel_CSSd\\node_modules\\@vue\\eslint-config-standard",
"author": {
"name": "Toru Nagashima"
},
"bugs": {
"url": "https://github.com/mysticatea/eslint-plugin-node/issues"
},
"bundleDependencies": false,
"dependencies": {
"ignore": "^3.3.6",
"minimatch": "^3.0.4",
"resolve": "^1.3.3",
"semver": "^5.4.1"
},
"deprecated": false,
"description": "Additional ESLint's rules for Node.js",
"devDependencies": {
"codecov": "^3.0.0",
"eslint": "^4.17.0",
"eslint-config-mysticatea": "^13.0.2",
"mocha": "^5.0.0",
"nyc": "^11.0.1",
"opener": "^1.4.3",
"rimraf": "^2.6.1",
"shelljs": "^0.8.1"
},
"engines": {
"node": ">=4"
},
"files": [
"lib"
],
"homepage": "https://github.com/mysticatea/eslint-plugin-node#readme",
"keywords": [
"eslint",
"eslintplugin",
"eslint-plugin",
"node",
"nodejs",
"ecmascript",
"shebang",
"file",
"path",
"import",
"require"
],
"license": "MIT",
"main": "lib/index.js",
"name": "eslint-plugin-node",
"peerDependencies": {
"eslint": ">=3.1.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mysticatea/eslint-plugin-node.git"
},
"scripts": {
"build": "node scripts/update.js",
"clean": "rimraf .nyc_output coverage",
"codecov": "nyc report -r lcovonly && codecov",
"coverage": "nyc report -r lcov && opener ./coverage/lcov-report/index.html",
"lint": "eslint lib tests/lib index.js",
"postversion": "git push && git push --tags",
"pretest": "npm run -s lint",
"preversion": "npm t && npm run -s build",
"test": "nyc npm run -s test:_mocha",
"test:_mocha": "_mocha tests/lib/**/*.js --reporter progress",
"watch": "npm run test:_mocha -- --watch --growl"
},
"version": "6.0.1"
}
@@ -0,0 +1 @@
package-lock.json
@@ -0,0 +1,124 @@
## 3.8.0
* Removed `promise/avoid-new` from recommended configuration
([#119](https://github.com/xjamundx/eslint-plugin-promise/pull/119))
* Ignored event listener callbacks in `promise/prefer-await-to-callbacks`
([#117](https://github.com/xjamundx/eslint-plugin-promise/pull/117))
* Ignored top-level awaits in `promise/prefer-await-to-then`
([#126](https://github.com/xjamundx/eslint-plugin-promise/pull/126))
* Added docs for `promise/no-nesting` and `promise/prefer-await-to-then`
([#120](https://github.com/xjamundx/eslint-plugin-promise/pull/120))
([#121](https://github.com/xjamundx/eslint-plugin-promise/pull/121))
## 3.7.0
* Added `promise/valid-params` rule
([#85](https://github.com/xjamundx/eslint-plugin-promise/pull/85))
* Added `promise/no-new-statics` rule
([#82](https://github.com/xjamundx/eslint-plugin-promise/pull/82))
* Added fixer for `promise/param-names` rule
([#99](https://github.com/xjamundx/eslint-plugin-promise/pull/99))
* Added rule documentation to each rule
([#91](https://github.com/xjamundx/eslint-plugin-promise/pull/91))
## 3.6.0
* Added `['catch']` support in `catch-or-return`
* Added `no-return-in-finally` rule
* Fixed some formatting in the docs
* Added `allowReject` option to `no-return-wrap`
* Added exceptions for `no-callback-in-promise`
## 3.5.0
* Added support for recommended settings using
`extends: plugin:promise/recommended`
## 3.4.2
* Fixed always return false positive with ternary (#31)
## 3.4.1
* fixed #49
## 3.4.0
* new rule: avoid-new
* new rule: no-promise-in-callback
* new rule: no-callback-in-promise
* new rule: no-nesting
## 3.3.2
* Removed eslint from peerDeps
## 3.3.1
* Updated engines with proper stuff
* Fixed bug for unreachable code
## 3.3.0
* Rule: `prefer-async-to-callbacks` added
* Rule: `prefer-async-to-then` added
## 3.2.1
* Fix: `no-return-wrap` rule missing from index.js
## 3.2.0
* Added `no-return-wrap` rule
## 3.1.0
* Added multiple terminationMethods
## 3.0.1
* Removed deprecated `always-catch` rule
* FIX: always-return error with "fn && fn()"
## 3.0.0
* Updated column and line numbers
* Added flow analysis for better handling of if statements
## 2.0.1
* Fixed type in docs
## 2.0.0
* ESLint 3.0 Support
## 1.3.2
* Updated tests to run on eslint 2.0
* Fixed some issues with `no-native` rule
## 1.3.1
* Actually added `no-native` rule
## 1.3.0
* Added `no-native` rule
## 1.2.0
* Allow `throw` in `always-return` rule
* Added `terminationMethod` option to `catch-or-return` rule
## 1.1.0
* Added `catch-or-return` rule
## 1.0.8
* Fixed crash issues
## 1.0.0 - 1.0.7
* Lots of basic feature updates and doc changes
@@ -0,0 +1,134 @@
# eslint-plugin-promise
Enforce best practices for JavaScript promises.
[![travis-ci](https://travis-ci.org/xjamundx/eslint-plugin-promise.svg)](https://travis-ci.org/xjamundx/eslint-plugin-promise)
[![npm version](https://badge.fury.io/js/eslint-plugin-promise.svg)](https://www.npmjs.com/package/eslint-plugin-promise)
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
* [Installation](#installation)
* [Usage](#usage)
* [Rules](#rules)
* [Maintainers](#maintainers)
* [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
You'll first need to install [ESLint](http://eslint.org):
```
$ npm install eslint --save-dev
```
Next, install `eslint-plugin-promise`:
```
$ npm install eslint-plugin-promise --save-dev
```
**Note:** If you installed ESLint globally (using the `-g` flag) then you must
also install `eslint-plugin-promise` globally.
## Usage
Add `promise` to the plugins section of your `.eslintrc` configuration file. You
can omit the `eslint-plugin-` prefix:
```json
{
"plugins": ["promise"]
}
```
Then configure the rules you want to use under the rules section.
```json
{
"rules": {
"promise/always-return": "error",
"promise/no-return-wrap": "error",
"promise/param-names": "error",
"promise/catch-or-return": "error",
"promise/no-native": "off",
"promise/no-nesting": "warn",
"promise/no-promise-in-callback": "warn",
"promise/no-callback-in-promise": "warn",
"promise/avoid-new": "warn",
"promise/no-new-statics": "error",
"promise/no-return-in-finally": "warn",
"promise/valid-params": "warn"
}
}
```
or start with the recommended rule set
```json
{
"extends": ["plugin:promise/recommended"]
}
```
## Rules
| rule | description | recommended | fixable |
| -------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------- | -------- |
| [`catch-or-return`][catch-or-return] | Enforces the use of `catch()` on un-returned promises. | :bangbang: | |
| [`no-return-wrap`][no-return-wrap] | Avoid wrapping values in `Promise.resolve` or `Promise.reject` when not needed. | :bangbang: | |
| [`param-names`][param-names] | Enforce consistent param names when creating new promises. | :bangbang: | :wrench: |
| [`always-return`][always-return] | Return inside each `then()` to create readable and reusable Promise chains. | :bangbang: | |
| [`no-native`][no-native] | In an ES5 environment, make sure to create a `Promise` constructor before using. | | |
| [`no-nesting`][no-nesting] | Avoid nested `then()` or `catch()` statements | :warning: | |
| [`no-promise-in-callback`][no-promise-in-callback] | Avoid using promises inside of callbacks | :warning: | |
| [`no-callback-in-promise`][no-callback-in-promise] | Avoid calling `cb()` inside of a `then()` (use [nodeify][] instead) | :warning: | |
| [`avoid-new`][avoid-new] | Avoid creating `new` promises outside of utility libs (use [pify][] instead) | | |
| [`no-new-statics`][no-new-statics] | Avoid calling `new` on a Promise static method | :bangbang: | |
| [`no-return-in-finally`][no-return-in-finally] | Disallow return statements in `finally()` | :warning: | |
| [`valid-params`][valid-params] | Ensures the proper number of arguments are passed to Promise functions | :warning: | |
| [`prefer-await-to-then`][prefer-await-to-then] | Prefer `await` to `then()` for reading Promise values | :seven: | |
| [`prefer-await-to-callbacks`][prefer-await-to-callbacks] | Prefer async/await to the callback pattern | :seven: | |
**Key**
| icon | description |
| ---------- | ----------------------------------------------- |
| :bangbang: | Reports as error in recommended configuration |
| :warning: | Reports as warning in recommended configuration |
| :seven: | ES2017 Async Await rules |
| :wrench: | Rule is fixable with `eslint --fix` |
## Maintainers
* Jamund Ferguson - [@xjamundx][]
* Macklin Underdown - [@macklinu][]
## License
* (c) MMXV jden <mailto:jason@denizac.org> - ISC license.
* (c) 2016 Jamund Ferguson <mailto:jamund@gmail.com> - ISC license.
[catch-or-return]: docs/rules/catch-or-return.md
[no-return-wrap]: docs/rules/no-return-wrap.md
[param-names]: docs/rules/param-names.md
[always-return]: docs/rules/always-return.md
[no-native]: docs/rules/no-native.md
[no-nesting]: docs/rules/no-nesting.md
[no-promise-in-callback]: docs/rules/no-promise-in-callback.md
[no-callback-in-promise]: docs/rules/no-callback-in-promise.md
[avoid-new]: docs/rules/avoid-new.md
[no-new-statics]: docs/rules/no-new-statics.md
[no-return-in-finally]: docs/rules/no-return-in-finally.md
[valid-params]: docs/rules/valid-params.md
[prefer-await-to-then]: docs/rules/prefer-await-to-then.md
[prefer-await-to-callbacks]: docs/rules/prefer-await-to-callbacks.md
[nodeify]: https://www.npmjs.com/package/nodeify
[pify]: https://www.npmjs.com/package/pify
[@macklinu]: https://github.com/macklinu
[@xjamundx]: https://github.com/xjamundx
@@ -0,0 +1,45 @@
'use strict'
module.exports = {
rules: {
'param-names': require('./rules/param-names'),
'no-return-wrap': require('./rules/no-return-wrap'),
'always-return': require('./rules/always-return'),
'catch-or-return': require('./rules/catch-or-return'),
'prefer-await-to-callbacks': require('./rules/prefer-await-to-callbacks'),
'prefer-await-to-then': require('./rules/prefer-await-to-then'),
'no-native': require('./rules/no-native'),
'no-callback-in-promise': require('./rules/no-callback-in-promise'),
'no-promise-in-callback': require('./rules/no-promise-in-callback'),
'no-nesting': require('./rules/no-nesting'),
'avoid-new': require('./rules/avoid-new'),
'no-new-statics': require('./rules/no-new-statics'),
'no-return-in-finally': require('./rules/no-return-in-finally'),
'valid-params': require('./rules/valid-params')
},
rulesConfig: {
'param-names': 1,
'always-return': 1,
'no-return-wrap': 1,
'no-native': 0,
'catch-or-return': 1
},
configs: {
recommended: {
rules: {
'promise/always-return': 'error',
'promise/no-return-wrap': 'error',
'promise/param-names': 'error',
'promise/catch-or-return': 'error',
'promise/no-native': 'off',
'promise/no-nesting': 'warn',
'promise/no-promise-in-callback': 'warn',
'promise/no-callback-in-promise': 'warn',
'promise/avoid-new': 'off',
'promise/no-new-statics': 'error',
'promise/no-return-in-finally': 'warn',
'promise/valid-params': 'warn'
}
}
}
}
@@ -0,0 +1,116 @@
{
"_from": "eslint-plugin-promise@^3.7.0",
"_id": "eslint-plugin-promise@3.8.0",
"_inBundle": false,
"_integrity": "sha512-JiFL9UFR15NKpHyGii1ZcvmtIqa3UTwiDAGb8atSffe43qJ3+1czVGN6UtkklpcJ2DVnqvTMzEKRaJdBkAL2aQ==",
"_location": "/@vue/eslint-config-standard/eslint-plugin-promise",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "eslint-plugin-promise@^3.7.0",
"name": "eslint-plugin-promise",
"escapedName": "eslint-plugin-promise",
"rawSpec": "^3.7.0",
"saveSpec": null,
"fetchSpec": "^3.7.0"
},
"_requiredBy": [
"/@vue/eslint-config-standard"
],
"_resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.8.0.tgz",
"_shasum": "65ebf27a845e3c1e9d6f6a5622ddd3801694b621",
"_spec": "eslint-plugin-promise@^3.7.0",
"_where": "D:\\Atom\\Git\\Laravel_CSSd\\node_modules\\@vue\\eslint-config-standard",
"author": {
"name": "jden",
"email": "jason@denizac.org"
},
"bugs": {
"url": "https://github.com/xjamundx/eslint-plugin-promise/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "Enforce best practices for JavaScript promises",
"devDependencies": {
"doctoc": "^1.3.0",
"eslint": "^4.17.0",
"eslint-config-prettier": "^2.9.0",
"eslint-config-standard": "^11.0.0-beta.0",
"eslint-plugin-eslint-plugin": "^1.4.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jest": "^21.12.2",
"eslint-plugin-node": "^6.0.0",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-promise": "./",
"eslint-plugin-standard": "^3.0.1",
"husky": "^0.14.3",
"jest": "^22.4.2",
"jest-runner-eslint": "^0.4.0",
"lint-staged": "^6.1.0",
"prettier": "^1.10.2"
},
"engines": {
"node": ">=4"
},
"homepage": "https://github.com/xjamundx/eslint-plugin-promise#readme",
"jest": {
"projects": [
{
"displayName": "test",
"testEnvironment": "node"
},
{
"runner": "jest-runner-eslint",
"displayName": "lint",
"testMatch": [
"<rootDir>/rules/**/*.js",
"<rootDir>/__tests__/**/*.js",
"<rootDir>/index.js"
]
}
]
},
"keywords": [
"eslint",
"eslintplugin",
"eslint-plugin",
"promise",
"promises"
],
"license": "ISC",
"lint-staged": {
"concurrent": false,
"linters": {
"{README.md,CONTRIBUTING.md}": [
"doctoc --maxlevel 3 --notitle",
"git add"
],
"*.js": [
"prettier --write",
"eslint --fix",
"git add"
],
"*.+(json|md)": [
"prettier --write",
"git add"
]
}
},
"name": "eslint-plugin-promise",
"prettier": {
"semi": false,
"singleQuote": true,
"proseWrap": "always"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/xjamundx/eslint-plugin-promise.git"
},
"scripts": {
"lint": "eslint index.js rules __tests__ --ignore-pattern '**/*.json'",
"precommit": "lint-staged",
"test": "jest"
},
"version": "3.8.0"
}
@@ -0,0 +1,157 @@
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
function isFunctionWithBlockStatement(node) {
if (node.type === 'FunctionExpression') {
return true
}
if (node.type === 'ArrowFunctionExpression') {
return node.body.type === 'BlockStatement'
}
return false
}
function isThenCallExpression(node) {
return (
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.property.name === 'then'
)
}
function isFirstArgument(node) {
return (
node.parent && node.parent.arguments && node.parent.arguments[0] === node
)
}
function isInlineThenFunctionExpression(node) {
return (
isFunctionWithBlockStatement(node) &&
isThenCallExpression(node.parent) &&
isFirstArgument(node)
)
}
function hasParentReturnStatement(node) {
if (node && node.parent && node.parent.type) {
// if the parent is a then, and we haven't returned anything, fail
if (isThenCallExpression(node.parent)) {
return false
}
if (node.parent.type === 'ReturnStatement') {
return true
}
return hasParentReturnStatement(node.parent)
}
return false
}
function peek(arr) {
return arr[arr.length - 1]
}
module.exports = {
meta: {
docs: {
url: getDocsUrl('always-return')
}
},
create: function(context) {
// funcInfoStack is a stack representing the stack of currently executing
// functions
// funcInfoStack[i].branchIDStack is a stack representing the currently
// executing branches ("codePathSegment"s) within the given function
// funcInfoStack[i].branchInfoMap is an object representing information
// about all branches within the given function
// funcInfoStack[i].branchInfoMap[j].good is a boolean representing whether
// the given branch explictly `return`s or `throw`s. It starts as `false`
// for every branch and is updated to `true` if a `return` or `throw`
// statement is found
// funcInfoStack[i].branchInfoMap[j].loc is a eslint SourceLocation object
// for the given branch
// example:
// funcInfoStack = [ { branchIDStack: [ 's1_1' ],
// branchInfoMap:
// { s1_1:
// { good: false,
// loc: <loc> } } },
// { branchIDStack: ['s2_1', 's2_4'],
// branchInfoMap:
// { s2_1:
// { good: false,
// loc: <loc> },
// s2_2:
// { good: true,
// loc: <loc> },
// s2_4:
// { good: false,
// loc: <loc> } } } ]
const funcInfoStack = []
function markCurrentBranchAsGood() {
const funcInfo = peek(funcInfoStack)
const currentBranchID = peek(funcInfo.branchIDStack)
if (funcInfo.branchInfoMap[currentBranchID]) {
funcInfo.branchInfoMap[currentBranchID].good = true
}
// else unreachable code
}
return {
ReturnStatement: markCurrentBranchAsGood,
ThrowStatement: markCurrentBranchAsGood,
onCodePathSegmentStart: function(segment, node) {
const funcInfo = peek(funcInfoStack)
funcInfo.branchIDStack.push(segment.id)
funcInfo.branchInfoMap[segment.id] = { good: false, node: node }
},
onCodePathSegmentEnd: function(segment, node) {
const funcInfo = peek(funcInfoStack)
funcInfo.branchIDStack.pop()
},
onCodePathStart: function(path, node) {
funcInfoStack.push({
branchIDStack: [],
branchInfoMap: {}
})
},
onCodePathEnd: function(path, node) {
const funcInfo = funcInfoStack.pop()
if (!isInlineThenFunctionExpression(node)) {
return
}
path.finalSegments.forEach(segment => {
const id = segment.id
const branch = funcInfo.branchInfoMap[id]
if (!branch.good) {
if (hasParentReturnStatement(branch.node)) {
return
}
// check shortcircuit syntax like `x && x()` and `y || x()``
const prevSegments = segment.prevSegments
for (let ii = prevSegments.length - 1; ii >= 0; --ii) {
const prevSegment = prevSegments[ii]
if (funcInfo.branchInfoMap[prevSegment.id].good) return
}
context.report({
message: 'Each then() should return a value or throw',
node: branch.node
})
}
})
}
}
}
}
@@ -0,0 +1,25 @@
/**
* Rule: avoid-new
* Avoid creating new promises outside of utility libraries.
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
module.exports = {
meta: {
docs: {
url: getDocsUrl('avoid-new')
}
},
create: function(context) {
return {
NewExpression: function(node) {
if (node.callee.name === 'Promise') {
context.report({ node, message: 'Avoid creating new promises.' })
}
}
}
}
}
@@ -0,0 +1,71 @@
/**
* Rule: catch-or-return
* Ensures that promises either include a catch() handler
* or are returned (to be handled upstream)
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const isPromise = require('./lib/is-promise')
module.exports = {
meta: {
docs: {
url: getDocsUrl('catch-or-return')
}
},
create: function(context) {
const options = context.options[0] || {}
const allowThen = options.allowThen
let terminationMethod = options.terminationMethod || 'catch'
if (typeof terminationMethod === 'string') {
terminationMethod = [terminationMethod]
}
return {
ExpressionStatement: function(node) {
if (!isPromise(node.expression)) {
return
}
// somePromise.then(a, b)
if (
allowThen &&
node.expression.type === 'CallExpression' &&
node.expression.callee.type === 'MemberExpression' &&
node.expression.callee.property.name === 'then' &&
node.expression.arguments.length === 2
) {
return
}
// somePromise.catch()
if (
node.expression.type === 'CallExpression' &&
node.expression.callee.type === 'MemberExpression' &&
terminationMethod.indexOf(node.expression.callee.property.name) !== -1
) {
return
}
// somePromise['catch']()
if (
node.expression.type === 'CallExpression' &&
node.expression.callee.type === 'MemberExpression' &&
node.expression.callee.property.type === 'Literal' &&
node.expression.callee.property.value === 'catch'
) {
return
}
context.report({
node,
message: 'Expected {{ terminationMethod }}() or return',
data: { terminationMethod }
})
}
}
}
}
@@ -0,0 +1,19 @@
'use strict'
const pkg = require('../../package')
const REPO_URL = 'https://github.com/xjamundx/eslint-plugin-promise'
/**
* Generates the URL to documentation for the given rule name. It uses the
* package version to build the link to a tagged version of the
* documentation file.
*
* @param {string} ruleName - Name of the eslint rule
* @returns {string} URL to the documentation for the given rule
*/
function getDocsUrl(ruleName) {
return `${REPO_URL}/tree/v${pkg.version}/docs/rules/${ruleName}.md`
}
module.exports = getDocsUrl
@@ -0,0 +1,16 @@
/**
* Library: Has Promis eCallback
* Makes sure that an Expression node is part of a promise
* with callback functions (like then() or catch())
*/
'use strict'
function hasPromiseCallback(node) {
if (node.type !== 'CallExpression') return
if (node.callee.type !== 'MemberExpression') return
const propertyName = node.callee.property.name
return propertyName === 'then' || propertyName === 'catch'
}
module.exports = hasPromiseCallback
@@ -0,0 +1,13 @@
'use strict'
const isNamedCallback = require('./is-named-callback')
function isCallingBack(node, exceptions) {
const isCallExpression = node.type === 'CallExpression'
const callee = node.callee || {}
const nameIsCallback = isNamedCallback(callee.name, exceptions)
const isCB = isCallExpression && nameIsCallback
return isCB
}
module.exports = isCallingBack
@@ -0,0 +1,20 @@
'use strict'
const isInsidePromise = require('./is-inside-promise')
function isInsideCallback(node) {
const isCallExpression =
node.type === 'FunctionExpression' ||
node.type === 'ArrowFunctionExpression' ||
node.type === 'FunctionDeclaration' // this may be controversial
// it's totally fine to use promises inside promises
if (isInsidePromise(node)) return
const name = node.params && node.params[0] && node.params[0].name
const firstArgIsError = name === 'err' || name === 'error'
const isInACallback = isCallExpression && firstArgIsError
return isInACallback
}
module.exports = isInsideCallback
@@ -0,0 +1,15 @@
'use strict'
function isInsidePromise(node) {
const isFunctionExpression =
node.type === 'FunctionExpression' ||
node.type === 'ArrowFunctionExpression'
const parent = node.parent || {}
const callee = parent.callee || {}
const name = (callee.property && callee.property.name) || ''
const parentIsPromise = name === 'then' || name === 'catch'
const isInCB = isFunctionExpression && parentIsPromise
return isInCB
}
module.exports = isInsidePromise
@@ -0,0 +1,14 @@
'use strict'
let callbacks = ['done', 'cb', 'callback', 'next']
module.exports = function isNamedCallback(potentialCallbackName, exceptions) {
for (let i = 0; i < exceptions.length; i++) {
callbacks = callbacks.filter(function(item) {
return item !== exceptions[i]
})
}
return callbacks.some(function(trueCallbackName) {
return potentialCallbackName === trueCallbackName
})
}
@@ -0,0 +1,36 @@
/**
* Library: isPromise
* Makes sure that an Expression node is part of a promise.
*/
'use strict'
const PROMISE_STATICS = require('./promise-statics')
function isPromise(expression) {
return (
// hello.then()
(expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'then') ||
// hello.catch()
(expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'catch') ||
// hello.finally()
(expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'finally') ||
// somePromise.ANYTHING()
(expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
isPromise(expression.callee.object)) ||
// Promise.STATIC_METHOD()
(expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.object.type === 'Identifier' &&
expression.callee.object.name === 'Promise' &&
PROMISE_STATICS[expression.callee.property.name])
)
}
module.exports = isPromise
@@ -0,0 +1,8 @@
'use strict'
module.exports = {
all: true,
race: true,
reject: true,
resolve: true
}
@@ -0,0 +1,53 @@
/**
* Rule: no-callback-in-promise
* Avoid calling back inside of a promise
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const hasPromiseCallback = require('./lib/has-promise-callback')
const isInsidePromise = require('./lib/is-inside-promise')
const isCallback = require('./lib/is-callback')
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-callback-in-promise')
}
},
create: function(context) {
return {
CallExpression: function(node) {
const options = context.options[0] || {}
const exceptions = options.exceptions || []
if (!isCallback(node, exceptions)) {
// in general we send you packing if you're not a callback
// but we also need to watch out for whatever.then(cb)
if (hasPromiseCallback(node)) {
const name =
node.arguments && node.arguments[0] && node.arguments[0].name
if (
name === 'callback' ||
name === 'cb' ||
name === 'next' ||
name === 'done'
) {
context.report({
node: node.arguments[0],
message: 'Avoid calling back inside of a promise.'
})
}
}
return
}
if (context.getAncestors().some(isInsidePromise)) {
context.report({
node,
message: 'Avoid calling back inside of a promise.'
})
}
}
}
}
}
@@ -0,0 +1,58 @@
// Borrowed from here:
// https://github.com/colonyamerican/eslint-plugin-cah/issues/3
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
function isDeclared(scope, ref) {
return scope.variables.some(function(variable) {
if (variable.name !== ref.identifier.name) {
return false
}
if (!variable.defs || !variable.defs.length) {
return false
}
return true
})
}
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-native')
}
},
create: function(context) {
const MESSAGE = '"{{name}}" is not defined.'
/**
* Checks for and reports reassigned constants
*
* @param {Scope} scope - an escope Scope object
* @returns {void}
* @private
*/
return {
'Program:exit': function() {
const scope = context.getScope()
scope.implicit.left.forEach(function(ref) {
if (ref.identifier.name !== 'Promise') {
return
}
if (!isDeclared(scope, ref)) {
context.report({
node: ref.identifier,
message: MESSAGE,
data: { name: ref.identifier.name }
})
}
})
}
}
}
}
@@ -0,0 +1,28 @@
/**
* Rule: no-nesting
* Avoid nesting your promises.
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const hasPromiseCallback = require('./lib/has-promise-callback')
const isInsidePromise = require('./lib/is-inside-promise')
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-nesting')
}
},
create: function(context) {
return {
CallExpression: function(node) {
if (!hasPromiseCallback(node)) return
if (context.getAncestors().some(isInsidePromise)) {
context.report({ node, message: 'Avoid nesting promises.' })
}
}
}
}
}
@@ -0,0 +1,29 @@
'use strict'
const PROMISE_STATICS = require('./lib/promise-statics')
const getDocsUrl = require('./lib/get-docs-url')
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-new-statics')
}
},
create(context) {
return {
NewExpression(node) {
if (
node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'Promise' &&
PROMISE_STATICS[node.callee.property.name]
) {
context.report({
node,
message: "Avoid calling 'new' on 'Promise.{{ name }}()'",
data: { name: node.callee.property.name }
})
}
}
}
}
}
@@ -0,0 +1,39 @@
/**
* Rule: no-promise-in-callback
* Discourage using promises inside of callbacks.
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const isPromise = require('./lib/is-promise')
const isInsideCallback = require('./lib/is-inside-callback')
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-promise-in-callback')
}
},
create: function(context) {
return {
CallExpression: function(node) {
if (!isPromise(node)) return
// if i'm returning the promise, it's probably not really a callback
// function, and I should be okay....
if (node.parent.type === 'ReturnStatement') return
// what about if the parent is an ArrowFunctionExpression
// would that imply an implicit return?
if (context.getAncestors().some(isInsideCallback)) {
context.report({
node: node.callee,
message: 'Avoid using promises inside of callbacks.'
})
}
}
}
}
}
@@ -0,0 +1,43 @@
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const isPromise = require('./lib/is-promise')
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-return-in-finally')
}
},
create: function(context) {
return {
CallExpression: function(node) {
if (isPromise(node)) {
if (
node.callee &&
node.callee.property &&
node.callee.property.name === 'finally'
) {
if (
node.arguments &&
node.arguments[0] &&
node.arguments[0].body &&
node.arguments[0].body.body
) {
if (
node.arguments[0].body.body.some(function(statement) {
return statement.type === 'ReturnStatement'
})
) {
context.report({
node: node.callee.property,
message: 'No return in finally'
})
}
}
}
}
}
}
}
}
@@ -0,0 +1,54 @@
/**
* Rule: no-return-wrap function
* Prevents uneccessary wrapping of results in Promise.resolve
* or Promise.reject as the Promise will do that for us
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const isPromise = require('./lib/is-promise')
const rejectMessage = 'Expected throw instead of Promise.reject'
const resolveMessage = 'Avoid wrapping return values in Promise.resolve'
function isInPromise(context) {
const expression = context.getAncestors().filter(function(node) {
return node.type === 'ExpressionStatement'
})[0]
return expression && expression.expression && isPromise(expression.expression)
}
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-return-wrap')
}
},
create: function(context) {
const options = context.options[0] || {}
const allowReject = options.allowReject
return {
ReturnStatement: function(node) {
if (isInPromise(context)) {
if (node.argument) {
if (node.argument.type === 'CallExpression') {
if (node.argument.callee.type === 'MemberExpression') {
if (node.argument.callee.object.name === 'Promise') {
if (node.argument.callee.property.name === 'resolve') {
context.report({ node, message: resolveMessage })
} else if (
!allowReject &&
node.argument.callee.property.name === 'reject'
) {
context.report({ node, message: rejectMessage })
}
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,42 @@
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
module.exports = {
meta: {
docs: {
url: getDocsUrl('param-names')
},
fixable: 'code'
},
create(context) {
return {
NewExpression(node) {
if (node.callee.name === 'Promise' && node.arguments.length === 1) {
const params = node.arguments[0].params
if (!params || !params.length) {
return
}
if (
params[0].name !== 'resolve' ||
(params[1] && params[1].name !== 'reject')
) {
context.report({
node,
message:
'Promise constructor parameters must be named resolve, reject',
fix(fixer) {
return [
fixer.replaceText(params[0], 'resolve'),
params[1] && fixer.replaceText(params[1], 'reject')
].filter(Boolean)
}
})
}
}
}
}
}
}
@@ -0,0 +1,63 @@
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const errorMessage = 'Avoid callbacks. Prefer Async/Await.'
module.exports = {
meta: {
docs: {
url: getDocsUrl('prefer-await-to-callbacks')
}
},
create(context) {
function checkLastParamsForCallback(node) {
const lastParam = node.params[node.params.length - 1] || {}
if (lastParam.name === 'callback' || lastParam.name === 'cb') {
context.report({ node: lastParam, message: errorMessage })
}
}
function isInsideYieldOrAwait() {
return context.getAncestors().some(parent => {
return (
parent.type === 'AwaitExpression' || parent.type === 'YieldExpression'
)
})
}
return {
CallExpression(node) {
// Callbacks aren't allowed.
if (node.callee.name === 'cb' || node.callee.name === 'callback') {
context.report({ node, message: errorMessage })
return
}
// Then-ables aren't allowed either.
const args = node.arguments
const lastArgIndex = args.length - 1
const arg = lastArgIndex > -1 && node.arguments[lastArgIndex]
if (
(arg && arg.type === 'FunctionExpression') ||
arg.type === 'ArrowFunctionExpression'
) {
// Ignore event listener callbacks.
if (
node.callee.property &&
(node.callee.property.name === 'on' ||
node.callee.property.name === 'once')
) {
return
}
if (arg.params && arg.params[0] && arg.params[0].name === 'err') {
if (!isInsideYieldOrAwait()) {
context.report({ node: arg, message: errorMessage })
}
}
}
},
FunctionDeclaration: checkLastParamsForCallback,
FunctionExpression: checkLastParamsForCallback,
ArrowFunctionExpression: checkLastParamsForCallback
}
}
}
@@ -0,0 +1,51 @@
/**
* Rule: prefer-await-to-then
* Discourage using then() and instead use async/await.
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
module.exports = {
meta: {
docs: {
url: getDocsUrl('prefer-await-to-then')
}
},
create: function(context) {
/** Returns true if node is inside yield or await expression. */
function isInsideYieldOrAwait() {
return context.getAncestors().some(parent => {
return (
parent.type === 'AwaitExpression' || parent.type === 'YieldExpression'
)
})
}
/**
* Returns true if node is created at the top-level scope.
* Await statements are not allowed at the top level,
* only within function declarations.
*/
function isTopLevelScoped() {
return context.getScope().block.type === 'Program'
}
return {
MemberExpression: function(node) {
if (isTopLevelScoped() || isInsideYieldOrAwait()) {
return
}
// if you're a then expression then you're probably a promise
if (node.property && node.property.name === 'then') {
context.report({
node: node.property,
message: 'Prefer await to then().'
})
}
}
}
}
}
@@ -0,0 +1,65 @@
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const isPromise = require('./lib/is-promise')
module.exports = {
meta: {
docs: {
description:
'Ensures the proper number of arguments are passed to Promise functions',
url: getDocsUrl('valid-params')
}
},
create(context) {
return {
CallExpression(node) {
if (!isPromise(node)) {
return
}
const name = node.callee.property.name
const numArgs = node.arguments.length
switch (name) {
case 'resolve':
case 'reject':
if (numArgs > 1) {
context.report({
node,
message:
'Promise.{{ name }}() requires 0 or 1 arguments, but received {{ numArgs }}',
data: { name, numArgs }
})
}
break
case 'then':
if (numArgs < 1 || numArgs > 2) {
context.report({
node,
message:
'Promise.{{ name }}() requires 1 or 2 arguments, but received {{ numArgs }}',
data: { name, numArgs }
})
}
break
case 'race':
case 'all':
case 'catch':
case 'finally':
if (numArgs !== 1) {
context.report({
node,
message:
'Promise.{{ name }}() requires 1 argument, but received {{ numArgs }}',
data: { name, numArgs }
})
}
break
default:
break
}
}
}
}
}
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Jamund Ferguson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,37 @@
# eslint-plugin-standard [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url]
[npm-image]: https://img.shields.io/npm/v/eslint-plugin-standard.svg
[npm-url]: https://npmjs.org/package/eslint-plugin-standard
[downloads-image]: https://img.shields.io/npm/dm/eslint-plugin-standard.svg
[downloads-url]: https://npmjs.org/package/eslint-plugin-standard
[standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg
[standard-url]: https://standardjs.com
ESlint Rules for the Standard Linter
### Usage
`npm install --save-dev eslint-plugin-standard`
### Configuration
```js
{
rules: {
'standard/object-curly-even-spacing': [2, "either"]
'standard/array-bracket-even-spacing': [2, "either"],
'standard/computed-property-even-spacing': [2, "even"]
'standard/no-callback-literal': [2, ["cb", "callback"]]
}
}
```
### Rules Explanations
There are several rules that were created specifically for the `standard` linter.
- `object-curly-even-spacing` - Like `object-curly-spacing` from ESLint except it has an `either` option which lets you have 1 or 0 spaces padding.
- `array-bracket-even-spacing` - Like `array-bracket-even-spacing` from ESLint except it has an `either` option which lets you have 1 or 0 spacing padding.
- `computed-property-even-spacing` - Like `computed-property-spacing` around ESLint except is has an `even` option which lets you have 1 or 0 spacing padding.
- `no-callback-literal` - Ensures that we strictly follow the callback pattern with `undefined`, `null` or an error object in the first position of a callback.
@@ -0,0 +1,16 @@
'use strict'
module.exports = {
rules: {
'array-bracket-even-spacing': require('./rules/array-bracket-even-spacing.js'),
'computed-property-even-spacing': require('./rules/computed-property-even-spacing.js'),
'object-curly-even-spacing': require('./rules/object-curly-even-spacing.js'),
'no-callback-literal': require('./rules/no-callback-literal.js')
},
rulesConfig: {
'object-curly-even-spacing': 0,
'array-bracket-even-spacing': 0,
'computed-property-even-spacing': 0,
'no-callback-literal': 0
}
}
@@ -0,0 +1,60 @@
{
"_from": "eslint-plugin-standard@^3.1.0",
"_id": "eslint-plugin-standard@3.1.0",
"_inBundle": false,
"_integrity": "sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w==",
"_location": "/@vue/eslint-config-standard/eslint-plugin-standard",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "eslint-plugin-standard@^3.1.0",
"name": "eslint-plugin-standard",
"escapedName": "eslint-plugin-standard",
"rawSpec": "^3.1.0",
"saveSpec": null,
"fetchSpec": "^3.1.0"
},
"_requiredBy": [
"/@vue/eslint-config-standard"
],
"_resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz",
"_shasum": "2a9e21259ba4c47c02d53b2d0c9135d4b1022d47",
"_spec": "eslint-plugin-standard@^3.1.0",
"_where": "D:\\Atom\\Git\\Laravel_CSSd\\node_modules\\@vue\\eslint-config-standard",
"author": {
"name": "Jamund Ferguson",
"email": "jamund@gmail.com"
},
"bugs": {
"url": "https://github.com/xjamundx/eslint-plugin-standard/issues"
},
"bundleDependencies": false,
"dependencies": {},
"deprecated": false,
"description": "ESlint Plugin for the Standard Linter",
"devDependencies": {
"eslint": "^4.19.1",
"mocha": "^5.1.1",
"standard": "*"
},
"homepage": "https://github.com/xjamundx/eslint-plugin-standard#readme",
"keywords": [
"eslint",
"eslintplugin"
],
"license": "MIT",
"main": "index.js",
"name": "eslint-plugin-standard",
"peerDependencies": {
"eslint": ">=3.19.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/xjamundx/eslint-plugin-standard.git"
},
"scripts": {
"test": "standard && mocha tests/"
},
"version": "3.1.0"
}
@@ -0,0 +1,197 @@
'use strict'
/**
* @fileoverview Disallows or enforces spaces inside of array brackets.
* @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
* @copyright 2014 Brandyn Bennett. All rights reserved.
* @copyright 2014 Michael Ficarra. No rights reserved.
* @copyright 2014 Vignesh Anand. All rights reserved.
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
url: 'https://github.com/xjamundx/eslint-plugin-standard#rules-explanations'
}
},
create: function (context) {
var spaced = context.options[0] === 'always'
var either = context.options[0] === 'either'
/**
* Determines whether an option is set, relative to the spacing option.
* If spaced is "always", then check whether option is set to false.
* If spaced is "never", then check whether option is set to true.
* @param {Object} option - The option to exclude.
* @returns {boolean} Whether or not the property is excluded.
*/
function isOptionSet (option) {
return context.options[1] != null ? context.options[1][option] === !spaced : false
}
var options = {
either: either,
spaced: spaced,
singleElementException: isOptionSet('singleValue'),
objectsInArraysException: isOptionSet('objectsInArrays'),
arraysInArraysException: isOptionSet('arraysInArrays')
}
// --------------------------------------------------------------------------
// Helpers
// --------------------------------------------------------------------------
/**
* Determines whether two adjacent tokens are have whitespace between them.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not there is space between the tokens.
*/
function isSpaced (left, right) {
return left.range[1] < right.range[0]
}
/**
* Determines whether two adjacent tokens are on the same line.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not the tokens are on the same line.
*/
function isSameLine (left, right) {
return left.loc.start.line === right.loc.start.line
}
/**
* Reports that there shouldn't be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoBeginningSpace (node, token) {
context.report(node, token.loc.start,
"There should be no space after '" + token.value + "'")
}
/**
* Reports that there shouldn't be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoEndingSpace (node, token) {
context.report(node, token.loc.start,
"There should be no space before '" + token.value + "'")
}
/**
* Reports that there should be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredBeginningSpace (node, token) {
context.report(node, token.loc.start,
"A space is required after '" + token.value + "'")
}
/**
* Reports that there should be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredEndingSpace (node, token) {
context.report(node, token.loc.start,
"A space is required before '" + token.value + "'")
}
/**
* Checks if a start and end brace in a node are spaced evenly
* and not too long (>1 space)
* @param node
* @param start
* @param end
* @returns {boolean}
*/
function isEvenlySpacedAndNotTooLong (node, start, end) {
var expectedSpace = start[1].range[0] - start[0].range[1]
var endSpace = end[1].range[0] - end[0].range[1]
return endSpace === expectedSpace && endSpace <= 1
}
/**
* Validates the spacing around array brackets
* @param {ASTNode} node - The node we're checking for spacing
* @returns {void}
*/
function validateArraySpacing (node) {
if (node.elements.length === 0) {
return
}
var first = context.getFirstToken(node)
var second = context.getFirstToken(node, 1)
var penultimate = context.getLastToken(node, 1)
var last = context.getLastToken(node)
var openingBracketMustBeSpaced =
(options.objectsInArraysException && second.value === '{') ||
(options.arraysInArraysException && second.value === '[') ||
(options.singleElementException && node.elements.length === 1)
? !options.spaced : options.spaced
var closingBracketMustBeSpaced =
(options.objectsInArraysException && penultimate.value === '}') ||
(options.arraysInArraysException && penultimate.value === ']') ||
(options.singleElementException && node.elements.length === 1)
? !options.spaced : options.spaced
// we only care about evenly spaced things
if (options.either) {
// newlines at any point means return
if (!isSameLine(first, last)) {
return
}
// confirm that the object expression/literal is spaced evenly
if (!isEvenlySpacedAndNotTooLong(node, [first, second], [penultimate, last])) {
context.report(node, 'Expected consistent spacing')
}
return
}
if (isSameLine(first, second)) {
if (openingBracketMustBeSpaced && !isSpaced(first, second)) {
reportRequiredBeginningSpace(node, first)
}
if (!openingBracketMustBeSpaced && isSpaced(first, second)) {
reportNoBeginningSpace(node, first)
}
}
if (isSameLine(penultimate, last)) {
if (closingBracketMustBeSpaced && !isSpaced(penultimate, last)) {
reportRequiredEndingSpace(node, last)
}
if (!closingBracketMustBeSpaced && isSpaced(penultimate, last)) {
reportNoEndingSpace(node, last)
}
}
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
ArrayPattern: validateArraySpacing,
ArrayExpression: validateArraySpacing
}
}
}
@@ -0,0 +1,161 @@
'use strict'
/**
* @fileoverview Disallows or enforces spaces inside computed properties.
* @author Jamund Ferguson
* @copyright 2015 Jamund Ferguson. All rights reserved.
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
url: 'https://github.com/xjamundx/eslint-plugin-standard#rules-explanations'
}
},
create: function (context) {
var propertyNameMustBeSpaced = context.options[0] === 'always' // default is "never"
var propertyNameMustBeEven = context.options[0] === 'even' // default is "never"
// --------------------------------------------------------------------------
// Helpers
// --------------------------------------------------------------------------
/**
* Determines whether two adjacent tokens are have whitespace between them.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not there is space between the tokens.
*/
function isSpaced (left, right) {
return left.range[1] < right.range[0]
}
/**
* Determines whether two adjacent tokens are on the same line.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not the tokens are on the same line.
*/
function isSameLine (left, right) {
return left.loc.start.line === right.loc.start.line
}
/**
* Reports that there shouldn't be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoBeginningSpace (node, token) {
context.report(node, token.loc.start,
"There should be no space after '" + token.value + "'")
}
/**
* Reports that there shouldn't be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoEndingSpace (node, token) {
context.report(node, token.loc.start,
"There should be no space before '" + token.value + "'")
}
/**
* Reports that there should be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredBeginningSpace (node, token) {
context.report(node, token.loc.start,
"A space is required after '" + token.value + "'")
}
/**
* Reports that there should be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredEndingSpace (node, token) {
context.report(node, token.loc.start,
"A space is required before '" + token.value + "'")
}
/**
* Returns a function that checks the spacing of a node on the property name
* that was passed in.
* @param {String} propertyName The property on the node to check for spacing
* @returns {Function} A function that will check spacing on a node
*/
function checkSpacing (propertyName) {
return function (node) {
if (!node.computed) {
return
}
var property = node[propertyName]
var before = context.getTokenBefore(property)
var first = context.getFirstToken(property)
var last = context.getLastToken(property)
var after = context.getTokenAfter(property)
var startSpace, endSpace
if (propertyNameMustBeEven) {
if (!isSameLine(before, after)) {
context.report(node, 'Expected "[" and "]" to be on the same line')
return
}
startSpace = first.loc.start.column - before.loc.end.column
endSpace = after.loc.start.column - last.loc.end.column
if (startSpace !== endSpace || startSpace > 1) {
context.report(node, 'Expected 1 or 0 spaces around "[" and "]"')
}
return
}
if (isSameLine(before, first)) {
if (propertyNameMustBeSpaced) {
if (!isSpaced(before, first) && isSameLine(before, first)) {
reportRequiredBeginningSpace(node, before)
}
} else {
if (isSpaced(before, first)) {
reportNoBeginningSpace(node, before)
}
}
}
if (isSameLine(last, after)) {
if (propertyNameMustBeSpaced) {
if (!isSpaced(last, after) && isSameLine(last, after)) {
reportRequiredEndingSpace(node, after)
}
} else {
if (isSpaced(last, after)) {
reportNoEndingSpace(node, after)
}
}
}
}
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
Property: checkSpacing('key'),
MemberExpression: checkSpacing('property')
}
}
}
@@ -0,0 +1,75 @@
/**
* Ensures that the callback pattern is followed properly
* with an Error object (or undefined or null) in the first position.
*/
'use strict'
// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------
/**
* Determine if a node has a possiblity to be an Error object
* @param {ASTNode} node ASTNode to check
* @returns {boolean} True if there is a chance it contains an Error obj
*/
function couldBeError (node) {
switch (node.type) {
case 'Identifier':
case 'CallExpression':
case 'NewExpression':
case 'MemberExpression':
case 'TaggedTemplateExpression':
case 'YieldExpression':
return true // possibly an error object.
case 'AssignmentExpression':
return couldBeError(node.right)
case 'SequenceExpression':
var exprs = node.expressions
return exprs.length !== 0 && couldBeError(exprs[exprs.length - 1])
case 'LogicalExpression':
return couldBeError(node.left) || couldBeError(node.right)
case 'ConditionalExpression':
return couldBeError(node.consequent) || couldBeError(node.alternate)
default:
return node.value === null
}
}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
url: 'https://github.com/xjamundx/eslint-plugin-standard#rules-explanations'
}
},
create: function (context) {
var callbackNames = context.options[0] || ['callback', 'cb']
function isCallback (name) {
return callbackNames.indexOf(name) > -1
}
return {
CallExpression: function (node) {
var errorArg = node.arguments[0]
var calleeName = node.callee.name
if (errorArg && !couldBeError(errorArg) && isCallback(calleeName)) {
context.report(node, 'Unexpected literal in error position of callback.')
}
}
}
}
}
@@ -0,0 +1,252 @@
'use strict'
/**
* @fileoverview Disallows or enforces spaces inside of object literals.
* @author Jamund Ferguson
* @copyright 2014 Brandyn Bennett. All rights reserved.
* @copyright 2014 Michael Ficarra. No rights reserved.
* @copyright 2014 Vignesh Anand. All rights reserved.
* @copyright 2015 Jamund Ferguson. All rights reserved.
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
url: 'https://github.com/xjamundx/eslint-plugin-standard#rules-explanations'
}
},
create: function (context) {
var spaced = context.options[0] === 'always'
var either = context.options[0] === 'either'
/**
* Determines whether an option is set, relative to the spacing option.
* If spaced is "always", then check whether option is set to false.
* If spaced is "never", then check whether option is set to true.
* @param {Object} option - The option to exclude.
* @returns {boolean} Whether or not the property is excluded.
*/
function isOptionSet (option) {
return context.options[1] != null ? context.options[1][option] === !spaced : false
}
var options = {
spaced: spaced,
either: either,
arraysInObjectsException: isOptionSet('arraysInObjects'),
objectsInObjectsException: isOptionSet('objectsInObjects')
}
// --------------------------------------------------------------------------
// Helpers
// --------------------------------------------------------------------------
/**
* Determines whether two adjacent tokens are have whitespace between them.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not there is space between the tokens.
*/
function isSpaced (left, right) {
return left.range[1] < right.range[0]
}
/**
* Determines whether two adjacent tokens are on the same line.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not the tokens are on the same line.
*/
function isSameLine (left, right) {
return left.loc.start.line === right.loc.start.line
}
/**
* Reports that there shouldn't be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoBeginningSpace (node, token) {
context.report(node, token.loc.start,
"There should be no space after '" + token.value + "'")
}
/**
* Reports that there shouldn't be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoEndingSpace (node, token) {
context.report(node, token.loc.start,
"There should be no space before '" + token.value + "'")
}
/**
* Reports that there should be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredBeginningSpace (node, token) {
context.report(node, token.loc.start,
"A space is required after '" + token.value + "'")
}
/**
* Reports that there should be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredEndingSpace (node, token) {
context.report(node, token.loc.start,
"A space is required before '" + token.value + "'")
}
/**
* Checks if a start and end brace in a node are spaced evenly
* and not too long (>1 space)
* @param node
* @param start
* @param end
* @returns {boolean}
*/
function isEvenlySpacedAndNotTooLong (node, start, end) {
var expectedSpace = start[1].range[0] - start[0].range[1]
var endSpace = end[1].range[0] - end[0].range[1]
return endSpace === expectedSpace && endSpace <= 1
}
/**
* Determines if spacing in curly braces is valid.
* @param {ASTNode} node The AST node to check.
* @param {Token} first The first token to check (should be the opening brace)
* @param {Token} second The second token to check (should be first after the opening brace)
* @param {Token} penultimate The penultimate token to check (should be last before closing brace)
* @param {Token} last The last token to check (should be closing brace)
* @returns {void}
*/
function validateBraceSpacing (node, first, second, penultimate, last) {
var closingCurlyBraceMustBeSpaced =
(options.arraysInObjectsException && penultimate.value === ']') ||
(options.objectsInObjectsException && penultimate.value === '}')
? !options.spaced : options.spaced
// we only care about evenly spaced things
if (options.either) {
// newlines at any point means return
if (!isSameLine(first, last)) {
return
}
// confirm that the object expression/literal is spaced evenly
if (!isEvenlySpacedAndNotTooLong(node, [first, second], [penultimate, last])) {
context.report(node, 'Expected consistent spacing')
}
return
}
// { and key are on same line
if (isSameLine(first, second)) {
if (options.spaced && !isSpaced(first, second)) {
reportRequiredBeginningSpace(node, first)
}
if (!options.spaced && isSpaced(first, second)) {
reportNoBeginningSpace(node, first)
}
}
// final key and } ore on the same line
if (isSameLine(penultimate, last)) {
if (closingCurlyBraceMustBeSpaced && !isSpaced(penultimate, last)) {
reportRequiredEndingSpace(node, last)
}
if (!closingCurlyBraceMustBeSpaced && isSpaced(penultimate, last)) {
reportNoEndingSpace(node, last)
}
}
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
// var {x} = y
ObjectPattern: function (node) {
if (node.properties.length === 0) {
return
}
var firstSpecifier = node.properties[0]
var lastSpecifier = node.properties[node.properties.length - 1]
var first = context.getTokenBefore(firstSpecifier)
var second = context.getFirstToken(firstSpecifier)
var penultimate = context.getLastToken(lastSpecifier)
var last = context.getTokenAfter(lastSpecifier)
// support trailing commas
if (last.value === ',') {
penultimate = last
last = context.getTokenAfter(last)
}
validateBraceSpacing(node, first, second, penultimate, last)
},
// import {y} from 'x'
ImportDeclaration: function (node) {
var firstSpecifier = node.specifiers[0]
var lastSpecifier = node.specifiers[node.specifiers.length - 1]
// don't do anything for namespace or default imports
if (firstSpecifier && lastSpecifier && firstSpecifier.type === 'ImportSpecifier' && lastSpecifier.type === 'ImportSpecifier') {
var first = context.getTokenBefore(firstSpecifier)
var second = context.getFirstToken(firstSpecifier)
var penultimate = context.getLastToken(lastSpecifier)
var last = context.getTokenAfter(lastSpecifier)
validateBraceSpacing(node, first, second, penultimate, last)
}
},
// export {name} from 'yo'
ExportNamedDeclaration: function (node) {
if (!node.specifiers.length) {
return
}
var firstSpecifier = node.specifiers[0]
var lastSpecifier = node.specifiers[node.specifiers.length - 1]
var first = context.getTokenBefore(firstSpecifier)
var second = context.getFirstToken(firstSpecifier)
var penultimate = context.getLastToken(lastSpecifier)
var last = context.getTokenAfter(lastSpecifier)
validateBraceSpacing(node, first, second, penultimate, last)
},
// var y = {x: 'y'}
ObjectExpression: function (node) {
if (node.properties.length === 0) {
return
}
var first = context.getFirstToken(node)
var second = context.getFirstToken(node, 1)
var penultimate = context.getLastToken(node, 1)
var last = context.getLastToken(node)
validateBraceSpacing(node, first, second, penultimate, last)
}
}
}
}
+63
View File
@@ -0,0 +1,63 @@
{
"_from": "@vue/eslint-config-standard@^3.0.1",
"_id": "@vue/eslint-config-standard@3.0.5",
"_inBundle": false,
"_integrity": "sha512-qijT6OWUgsCO/MTC9Qpw4JtRL01cX0+kIikU0bXQxWXyh8WfNnfvxPouo7tKf7W28qqqiGmwDUnbvTB50SLcTw==",
"_location": "/@vue/eslint-config-standard",
"_phantomChildren": {
"ignore": "3.3.10",
"minimatch": "3.0.4",
"resolve": "1.10.0",
"semver": "5.7.0"
},
"_requested": {
"type": "range",
"registry": true,
"raw": "@vue/eslint-config-standard@^3.0.1",
"name": "@vue/eslint-config-standard",
"escapedName": "@vue%2feslint-config-standard",
"scope": "@vue",
"rawSpec": "^3.0.1",
"saveSpec": null,
"fetchSpec": "^3.0.1"
},
"_requiredBy": [
"#DEV:/"
],
"_resolved": "https://registry.npmjs.org/@vue/eslint-config-standard/-/eslint-config-standard-3.0.5.tgz",
"_shasum": "7d6ae809eaec90993c6033d9543f48f687b9ebf1",
"_spec": "@vue/eslint-config-standard@^3.0.1",
"_where": "D:\\Atom\\Git\\Laravel_CSSd",
"author": {
"name": "Evan You"
},
"bugs": {
"url": "https://github.com/vuejs/vue-cli/issues"
},
"bundleDependencies": false,
"dependencies": {
"eslint-config-standard": "^12.0.0-alpha.0",
"eslint-plugin-import": "^2.11.0",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.7.0",
"eslint-plugin-standard": "^3.1.0"
},
"deprecated": false,
"description": "eslint-config-standard for vue-cli",
"homepage": "https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/eslint-config-standard#readme",
"keywords": [
"vue",
"cli"
],
"license": "MIT",
"main": "index.js",
"name": "@vue/eslint-config-standard",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue-cli.git"
},
"version": "3.0.5"
}