pax_global_header00006660000000000000000000000064136716635210014524gustar00rootroot0000000000000052 comment=23709f84c8e21f1a38f61ad689c82a8763e0737f eslint-utils-2.1.0/000077500000000000000000000000001367166352100141605ustar00rootroot00000000000000eslint-utils-2.1.0/.codecov.yml000066400000000000000000000000151367166352100163770ustar00rootroot00000000000000comment: off eslint-utils-2.1.0/.eslintignore000066400000000000000000000001271367166352100166630ustar00rootroot00000000000000/.nyc_output /coverage /node_modules /index.* /test.* !.vuepress /docs/.vuepress/dist eslint-utils-2.1.0/.eslintrc.yml000066400000000000000000000004231367166352100166030ustar00rootroot00000000000000root: true extends: - plugin:@mysticatea/es2015 overrides: - files: - src/**/*.js - test/**/*.js extends: plugin:@mysticatea/+modules rules: init-declarations: "off" "@mysticatea/node/no-unsupported-features/es-syntax": - error - ignores: [modules] eslint-utils-2.1.0/.esmrc000066400000000000000000000001001367166352100152610ustar00rootroot00000000000000{ "cache": true, "debug": true, "sourceMap": true } eslint-utils-2.1.0/.gitattributes000066400000000000000000000000231367166352100170460ustar00rootroot00000000000000* text=auto eol=lf eslint-utils-2.1.0/.github/000077500000000000000000000000001367166352100155205ustar00rootroot00000000000000eslint-utils-2.1.0/.github/FUNDING.yml000066400000000000000000000000251367166352100173320ustar00rootroot00000000000000github: - mysticatea eslint-utils-2.1.0/.github/workflows/000077500000000000000000000000001367166352100175555ustar00rootroot00000000000000eslint-utils-2.1.0/.github/workflows/CI.yml000066400000000000000000000030241367166352100205720ustar00rootroot00000000000000name: CI on: push: branches: [master] pull_request: branches: [master] schedule: - cron: 0 0 * * 0 jobs: lint: name: Lint runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 with: fetch-depth: 1 - name: Install Node.js uses: actions/setup-node@v1 with: node-version: 12 - name: Install Packages run: npm install - name: Lint run: npm run -s lint test: name: Test strategy: matrix: node: [12, 10, 8, 6] eslint: [6, 5] exclude: # ESLint 6 doesn't support Node 6. - node: 6 eslint: 6 # Run ESLint 5 on only the newest and oldest Node. - node: 10 eslint: 5 - node: 8 eslint: 5 runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 with: fetch-depth: 1 - name: Install Node.js v${{ matrix.node }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - name: Install Packages run: | if [ ${{ matrix.node }} -eq 6 ]; then npm install --global npm@^6.0.0 fi npm install - name: Install ESLint v${{ matrix.eslint }} run: npm install --no-save eslint@^${{ matrix.eslint }}.0.0 - name: Build run: npm run -s build - name: Test run: npm run -s test:mocha - name: Send Coverage run: npm run -s codecov env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} eslint-utils-2.1.0/.gitignore000066400000000000000000000001131367166352100161430ustar00rootroot00000000000000/.nyc_output /coverage /docs/.vuepress/dist /node_modules /index.* /test.* eslint-utils-2.1.0/.npmrc000066400000000000000000000000251367166352100152750ustar00rootroot00000000000000package-lock = false eslint-utils-2.1.0/.nycrc000066400000000000000000000001451367166352100152770ustar00rootroot00000000000000{ "include": ["src/**/*.js"], "reporter": ["lcov", "text-summary"], "require": ["esm"] } eslint-utils-2.1.0/.vscode/000077500000000000000000000000001367166352100155215ustar00rootroot00000000000000eslint-utils-2.1.0/.vscode/extensions.json000066400000000000000000000001041367166352100206060ustar00rootroot00000000000000{ "recommendations": [ "dbaeumer.vscode-eslint" ] } eslint-utils-2.1.0/.vscode/settings.json000066400000000000000000000002761367166352100202610ustar00rootroot00000000000000{ "[javascript]": { "editor.formatOnSave": false, "editor.formatOnType": false, "editor.codeActionsOnSave": { "source.fixAll": true } } } eslint-utils-2.1.0/LICENSE000066400000000000000000000020571367166352100151710ustar00rootroot00000000000000MIT License Copyright (c) 2018 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. eslint-utils-2.1.0/README.md000066400000000000000000000030711367166352100154400ustar00rootroot00000000000000# eslint-utils [![npm version](https://img.shields.io/npm/v/eslint-utils.svg)](https://www.npmjs.com/package/eslint-utils) [![Downloads/month](https://img.shields.io/npm/dm/eslint-utils.svg)](http://www.npmtrends.com/eslint-utils) [![Build Status](https://github.com/mysticatea/eslint-utils/workflows/CI/badge.svg)](https://github.com/mysticatea/eslint-utils/actions) [![Coverage Status](https://codecov.io/gh/mysticatea/eslint-utils/branch/master/graph/badge.svg)](https://codecov.io/gh/mysticatea/eslint-utils) [![Dependency Status](https://david-dm.org/mysticatea/eslint-utils.svg)](https://david-dm.org/mysticatea/eslint-utils) ## 🏁 Goal This package provides utility functions and classes for make ESLint custom rules. For examples: - [getStaticValue](https://eslint-utils.mysticatea.dev/api/ast-utils.html#getstaticvalue) evaluates static value on AST. - [ReferenceTracker](https://eslint-utils.mysticatea.dev/api/scope-utils.html#referencetracker-class) checks the members of modules/globals as handling assignments and destructuring. ## 📖 Usage See [documentation](https://eslint-utils.mysticatea.dev/). ## 📰 Changelog See [releases](https://github.com/mysticatea/eslint-utils/releases). ## ❤️ Contributing Welcome contributing! Please use GitHub's Issues/PRs. ### Development Tools - `npm test` runs tests and measures coverage. - `npm run clean` removes the coverage result of `npm test` command. - `npm run coverage` shows the coverage result of the last `npm test` command. - `npm run lint` runs ESLint. - `npm run watch` runs tests on each file change. eslint-utils-2.1.0/docs/000077500000000000000000000000001367166352100151105ustar00rootroot00000000000000eslint-utils-2.1.0/docs/.vuepress/000077500000000000000000000000001367166352100170425ustar00rootroot00000000000000eslint-utils-2.1.0/docs/.vuepress/config.js000066400000000000000000000015441367166352100206510ustar00rootroot00000000000000"use strict" module.exports = { title: "eslint-utils", description: "Utilities for ESLint plugins and custom rules.", serviceWorker: true, themeConfig: { repo: "mysticatea/eslint-utils", docsRepo: "mysticatea/eslint-utils", docsDir: "docs", docsBranch: "master", editLinks: true, sidebar: { "/": [ { title: "Guide", collapsable: false, children: ["/"], }, { title: "API Reference", collapsable: false, children: [ "/api/ast-utils", "/api/scope-utils", "/api/token-utils", ], }, ], }, }, } eslint-utils-2.1.0/docs/.vuepress/public/000077500000000000000000000000001367166352100203205ustar00rootroot00000000000000eslint-utils-2.1.0/docs/.vuepress/public/.nojekyll000066400000000000000000000000001367166352100221360ustar00rootroot00000000000000eslint-utils-2.1.0/docs/.vuepress/styles/000077500000000000000000000000001367166352100203655ustar00rootroot00000000000000eslint-utils-2.1.0/docs/.vuepress/styles/palette.styl000066400000000000000000000000271367166352100227370ustar00rootroot00000000000000$accentColor = #463fd4 eslint-utils-2.1.0/docs/README.md000066400000000000000000000020171367166352100163670ustar00rootroot00000000000000# Getting Started [![npm version](https://img.shields.io/npm/v/eslint-utils.svg)](https://www.npmjs.com/package/eslint-utils) [![Downloads/month](https://img.shields.io/npm/dm/eslint-utils.svg)](http://www.npmtrends.com/eslint-utils) [![Build Status](https://travis-ci.org/mysticatea/eslint-utils.svg?branch=master)](https://travis-ci.org/mysticatea/eslint-utils) [![Coverage Status](https://codecov.io/gh/mysticatea/eslint-utils/branch/master/graph/badge.svg)](https://codecov.io/gh/mysticatea/eslint-utils) [![Dependency Status](https://david-dm.org/mysticatea/eslint-utils.svg)](https://david-dm.org/mysticatea/eslint-utils) ## 🏁 Goal `eslint-utils` package provides utility functions and classes for make ESLint custom rules. ## 💿 Installation Use [npm](https://www.npmjs.com/) or a compatible tool to install. ``` npm install eslint-utils ``` ::: tip Requirements `eslint-utils` requires Node.js `6.5.0` or newer versions. ::: ## 📖 Usage ```js const utils = require("eslint-utils") // use it to define rules... ``` eslint-utils-2.1.0/docs/api/000077500000000000000000000000001367166352100156615ustar00rootroot00000000000000eslint-utils-2.1.0/docs/api/ast-utils.md000066400000000000000000000416421367166352100201370ustar00rootroot00000000000000# AST utilities ---- ## getFunctionHeadLocation ```js const loc = utils.getFunctionHeadLocation(node, sourceCode) ``` Get the proper location of a given function node to report.
Show the location examples: ``` - `function foo() {}` ^^^^^^^^^^^^ - `(function foo() {})` ^^^^^^^^^^^^ - `(function() {})` ^^^^^^^^ - `function* foo() {}` ^^^^^^^^^^^^^ - `(function* foo() {})` ^^^^^^^^^^^^^ - `(function*() {})` ^^^^^^^^^ - `() => {}` ^^ - `async () => {}` ^^ - `({ foo: function foo() {} })` ^^^^^^^^^^^^^^^^^ - `({ foo: function() {} })` ^^^^^^^^^^^^^ - `({ ['foo']: function() {} })` ^^^^^^^^^^^^^^^^^ - `({ [foo]: function() {} })` ^^^^^^^^^^^^^^^ - `({ foo() {} })` ^^^ - `({ foo: function* foo() {} })` ^^^^^^^^^^^^^^^^^^ - `({ foo: function*() {} })` ^^^^^^^^^^^^^^ - `({ ['foo']: function*() {} })` ^^^^^^^^^^^^^^^^^^ - `({ [foo]: function*() {} })` ^^^^^^^^^^^^^^^^ - `({ *foo() {} })` ^^^^ - `({ foo: async function foo() {} })` ^^^^^^^^^^^^^^^^^^^^^^^ - `({ foo: async function() {} })` ^^^^^^^^^^^^^^^^^^^ - `({ ['foo']: async function() {} })` ^^^^^^^^^^^^^^^^^^^^^^^ - `({ [foo]: async function() {} })` ^^^^^^^^^^^^^^^^^^^^^ - `({ async foo() {} })` ^^^^^^^^^ - `({ get foo() {} })` ^^^^^^^ - `({ set foo(a) {} })` ^^^^^^^ - `class A { constructor() {} }` ^^^^^^^^^^^ - `class A { foo() {} }` ^^^ - `class A { *foo() {} }` ^^^^ - `class A { async foo() {} }` ^^^^^^^^^ - `class A { ['foo']() {} }` ^^^^^^^ - `class A { *['foo']() {} }` ^^^^^^^^ - `class A { async ['foo']() {} }` ^^^^^^^^^^^^^ - `class A { [foo]() {} }` ^^^^^ - `class A { *[foo]() {} }` ^^^^^^ - `class A { async [foo]() {} }` ^^^^^^^^^^^ - `class A { get foo() {} }` ^^^^^^^ - `class A { set foo(a) {} }` ^^^^^^^ - `class A { static foo() {} }` ^^^^^^^^^^ - `class A { static *foo() {} }` ^^^^^^^^^^^ - `class A { static async foo() {} }` ^^^^^^^^^^^^^^^^ - `class A { static get foo() {} }` ^^^^^^^^^^^^^^ - `class A { static set foo(a) {} }` ^^^^^^^^^^^^^^ ```
### Parameters Name | Type | Description :-----|:-----|:------------ node | Node | The function node to get the location. This should be any of `FunctionDeclaration`, `FunctionExpression`, and `ArrowFunctionExpression` node. sourceCode | SourceCode | The source code object to get tokens. ### Return value The location object. ### Example ```js{12} const { getFunctionHeadLocation } = require("eslint-utils") module.exports = { meta: {}, create(context) { const sourceCode = context.getSourceCode() return { FunctionDeclaration(node) { context.report({ node, loc: getFunctionHeadLocation(node, sourceCode), message: "disallow this function!", }) }, } }, } ``` ---- ## getFunctionNameWithKind ```js const name = utils.getFunctionNameWithKind(node) ``` Get the name and kind of a given function node.
Show the name and kind examples: ``` - `function foo() {}` .................... `function 'foo'` - `(function foo() {})` .................. `function 'foo'` - `(function() {})` ...................... `function` - `function* foo() {}` ................... `generator function 'foo'` - `(function* foo() {})` ................. `generator function 'foo'` - `(function*() {})` ..................... `generator function` - `() => {}` ............................. `arrow function` - `async () => {}` ....................... `async arrow function` - `const foo = () => {}` ................. `arrow function 'foo'` - `const foo = async () => {}` ........... `async arrow function 'foo'` - `foo = () => {}` ....................... `arrow function 'foo'` - `foo = async () => {}` ................. `async arrow function 'foo'` - `({ foo: function foo() {} })` ......... `method 'foo'` - `({ foo: function() {} })` ............. `method 'foo'` - `({ ['foo']: function() {} })` ......... `method 'foo'` - `({ [foo]: function() {} })` ........... `method` - `({ foo() {} })` ....................... `method 'foo'` - `({ foo: function* foo() {} })` ........ `generator method 'foo'` - `({ foo: function*() {} })` ............ `generator method 'foo'` - `({ ['foo']: function*() {} })` ........ `generator method 'foo'` - `({ [foo]: function*() {} })` .......... `generator method` - `({ *foo() {} })` ...................... `generator method 'foo'` - `({ foo: async function foo() {} })` ... `async method 'foo'` - `({ foo: async function() {} })` ....... `async method 'foo'` - `({ ['foo']: async function() {} })` ... `async method 'foo'` - `({ [foo]: async function() {} })` ..... `async method` - `({ async foo() {} })` ................. `async method 'foo'` - `({ get foo() {} })` ................... `getter 'foo'` - `({ set foo(a) {} })` .................. `setter 'foo'` - `class A { constructor() {} }` ......... `constructor` - `class A { foo() {} }` ................. `method 'foo'` - `class A { *foo() {} }` ................ `generator method 'foo'` - `class A { async foo() {} }` ........... `async method 'foo'` - `class A { ['foo']() {} }` ............. `method 'foo'` - `class A { *['foo']() {} }` ............ `generator method 'foo'` - `class A { async ['foo']() {} }` ....... `async method 'foo'` - `class A { [foo]() {} }` ............... `method` - `class A { *[foo]() {} }` .............. `generator method` - `class A { async [foo]() {} }` ......... `async method` - `class A { get foo() {} }` ............. `getter 'foo'` - `class A { set foo(a) {} }` ............ `setter 'foo'` - `class A { static foo() {} }` .......... `static method 'foo'` - `class A { static *foo() {} }` ......... `static generator method 'foo'` - `class A { static async foo() {} }` .... `static async method 'foo'` - `class A { static get foo() {} }` ...... `static getter 'foo'` - `class A { static set foo(a) {} }` ..... `static setter 'foo'` ```
### Parameters Name | Type | Description :-----|:-----|:------------ node | Node | The function node to get the name and kind. This should be any of `FunctionDeclaration`, `FunctionExpression`, and `ArrowFunctionExpression` node. ### Return value The name and kind of the function. ### Example ```js{11} const { getFunctionNameWithKind } = require("eslint-utils") module.exports = { meta: {}, create(context) { return { FunctionDeclaration(node) { context.report({ node, message: "disallow this {{name}}!", data: { name: getFunctionNameWithKind(node) } }) }, } }, } ``` ---- ## getPropertyName ```js const name = utils.getPropertyName(node) const name = utils.getPropertyName(node, initialScope) ``` Get the property name of a given property node. If the node is a computed property, this tries to compute the property name by the [getStringIfConstant](#getstringifconstant) function. ### Parameters Name | Type | Description :-----|:-----|:------------ node | Node | The node to get that name. This shuld be any of `MemberExpression`, `Property`, and `MethodDefinition` node. initialScope | Scope or undefined | Optional. The scope object to find variables. ### Return value The property name of the node. If the property name is not constant then it returns `null`. ### Example ```js{8} const { getPropertyName } = require("eslint-utils") module.exports = { meta: {}, create(context) { return { MemberExpression(node) { const name = getPropertyName(node, context.getScope()) }, } }, } ``` ---- ## getStaticValue ```js const ret1 = utils.getStaticValue(node) const ret2 = utils.getStaticValue(node, initialScope) ``` Get the value of a given node if it can decide the value statically. If the 2nd parameter `initialScope` was given, this function tries to resolve identifier references which are in the given node as much as possible. In the resolving way, it does on the assumption that built-in global objects have not been modified. For example, it considers `Symbol.iterator`, ``String.raw`hello` ``, and `Object.freeze({a: 1}).a` as static. For another complex example, this function can evaluate the following cases on AST: ```js{6} const eventName = "click" const aMap = Object.freeze({ click: 777 }) ;`on${eventName} : ${aMap[eventName]}` // evaluated to "onclick : 777" ``` ### Parameters Name | Type | Description :-----|:-----|:------------ node | Node | The node to get that the value. initialScope | Scope or undefined | Optional. The scope object to find variables. ### Return value The `{ value: any }` shaped object. The `value` property is the static value. If it couldn't compute the static value of the node, it returns `null`. ### Example ```js{8} const { getStaticValue } = require("eslint-utils") module.exports = { meta: {}, create(context) { return { ExpressionStatement(node) { const evaluated = getStaticValue(node, context.getScope()) if (evaluated) { const staticValue = evaluated.value // ... } }, } }, } ``` ---- ## getStringIfConstant ```js const str1 = utils.getStringIfConstant(node) const str2 = utils.getStringIfConstant(node, initialScope) ``` Get the string value of a given node. This function is a tiny wrapper of the [getStaticValue](#getstaticvalue) function. I.e., this is the same as below: ```js function getStringIfConstant(node, initialScope) { const evaluated = getStaticValue(node, initialScope) return evaluated && String(evaluated.value) } ``` ---- ## hasSideEffect ```js const ret = utils.hasSideEffect(node, sourceCode, options) ``` Check whether a given node has any side effect or not. The side effect means that it *may* modify a certain variable or object member. This function considers the node which contains the following types as the node which has side effects: - `AssignmentExpression` - `AwaitExpression` - `CallExpression` - `ImportExpression` - `NewExpression` - `UnaryExpression` (`[operator = "delete"]`) - `UpdateExpression` - `YieldExpression` - When `options.considerGetters` is `true`: - `MemberExpression` - When `options.considerImplicitTypeConversion` is `true`: - `BinaryExpression` (`[operator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "<<" | ">>" | ">>>" | "+" | "-" | "*" | "/" | "%" | "|" | "^" | "&" | "in"]`) - `MemberExpression` (`[computed = true]`) - `MethodDefinition` (`[computed = true]`) - `Property` (`[computed = true]`) - `UnaryExpression` (`[operator = "-" | "+" | "!" | "~"]`) ### Parameters Name | Type | Description :-----|:-----|:------------ node | Node | The node to check. sourceCode | SourceCode | The source code object to get visitor keys. options.considerGetters | boolean | Default is `false`. If `true` then it considers member accesses as the node which has side effects. options.considerImplicitTypeConversion | boolean | Default is `false`. If `true` then it considers implicit type conversion as the node which has side effects. ### Return value `true` if the node has a certain side effect. ### Example ```js{9} const { hasSideEffect } = require("eslint-utils") module.exports = { meta: {}, create(context) { const sourceCode = context.getSourceCode() return { ":expression"(node) { if (hasSideEffect(node, sourceCode)) { // ... } }, } }, } ``` ---- ## isParenthesized ```js const ret1 = utils.isParenthesized(times, node, sourceCode) const ret2 = utils.isParenthesized(node, sourceCode) ``` Check whether a given node is parenthesized or not. This function detects it correctly even if it's parenthesized by specific syntax. ```js // those `a` are not parenthesized. f(a); new C(a); if (a) {} switch (a) {} while (a) {} do {} while (a); with (a) {} // those `b` are parenthesized. f((b)); new C((b)); if ((b)) {} switch ((b)) {} while ((b)) {} do {} while ((b)); with ((b)) {} ``` ### Parameters Name | Type | Description :-----|:-----|:------------ times | number | Optional. The number of redundant parenthesized. Default is `1`. node | Node | The node to check. sourceCode | SourceCode | The source code object to get tokens. ### Return value `true` if the node is parenthesized. If `times` was given, it returns `true` only if the node is parenthesized the `times` times. For example, `isParenthesized(2, node, sourceCode)` returns `true` for `((foo))`, but not for `(foo)`. ### Example ```js{9} const { isParenthesized } = require("eslint-utils") module.exports = { meta: {}, create(context) { const sourceCode = context.getSourceCode() return { ":expression"(node) { if (isParenthesized(node, sourceCode)) { // ... } }, } }, } ``` ---- ## PatternMatcher class ```js const matcher = new utils.PatternMatcher(pattern, options) ``` The class to find a pattern in strings as handling escape sequences. It ignores the found pattern if it's escaped with `\`. ### Parameters Name | Type | Description :-----|:-----|:------------ pattern | RegExp | The regular expression pattern to find. options.escaped | boolean | Optional. Default is `false`. If `true` then this matches to escaped patterns. ## matcher.execAll ```js const iterator = matcher.execAll(str) ``` Iterate all matched parts in a given string. ### Parameters Name | Type | Description :-----|:-----|:------------ str | string | The string to find this pattern. ### Return value The generator which iterates [RegExpExecArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec#Description) object. ### Example ```js{9} const { PatternMatcher } = require("eslint-utils") const matcher = new PatternMatcher(/\(\?<[_$\w]/g) module.exports = { meta: {}, create(context) { return { "Literal[regex]"(node) { for (const { index } of matcher.execAll(node.regex.pattern)) { context.report({ node, loc: { line: node.loc.start.line, column: node.loc.start.column + index }, message: "ES2018 RegExp named capture groups", }) } }, } }, } ``` ## matcher.test ```js const matched = matcher.test(str) ``` Check whether this pattern matches a given string or not. ### Parameters Name | Type | Description :-----|:-----|:------------ str | string | The string to find this pattern. ### Return value `true` if this pattern matched the string. ### Example ```js{9} const { PatternMatcher } = require("eslint-utils") const matcher = new PatternMatcher(/\(\?<[_$\w]/g) module.exports = { meta: {}, create(context) { return { "Literal[regex]"(node) { if (matcher.test(node.regex.pattern)) { context.report({ node, message: "RegExp has ES2018 named capture groups", }) } }, } }, } ``` ## matcher[Symbol.replace] ```js const replacedStr = str.replace(matcher, replacer) ``` Replace all matched parts by a given replacer. ### Parameters Name | Type | Description :-----|:-----|:------------ str | string | The string to be replaced. replacer | string or function | The string or function to replace each matched part. This is the same as the 2nd parameter of [String.prototype.replace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace). ### Return value The replaced result. ### Example ```js{9} const { PatternMatcher } = require("eslint-utils") const matcher = new PatternMatcher(/\\p{Script=Greek}/g) module.exports = { meta: {}, create(context) { return { "Literal[regex]"(node) { const replacedPattern = node.regex.pattern.replace( matcher, "[\\u0370-\\u0373\\u0375-\\u0377\\u037A-\\u037D\\u037F\\u0384\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03E1\\u03F0-\\u03FF\\u1D26-\\u1D2A\\u1D5D-\\u1D61\\u1D66-\\u1D6A\\u1DBF\\u1F00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FC4\\u1FC6-\\u1FD3\\u1FD6-\\u1FDB\\u1FDD-\\u1FEF\\u1FF2-\\u1FF4\\u1FF6-\\u1FFE\\u2126\\uAB65]|\\uD800[\\uDD40-\\uDD8E\\uDDA0]|\\uD834[\\uDE00-\\uDE45]" ) }, } }, } ``` eslint-utils-2.1.0/docs/api/scope-utils.md000066400000000000000000000223331367166352100204550ustar00rootroot00000000000000# Scope analysis utilities ## findVariable ```js const variable = utils.findVariable(initialScope, name) ``` Get the variable of a given name. ### Parameters Name | Type | Description :-----|:-----|:------------ initialScope | Scope | The scope object to start finding variables. name | string or Node | The variable name to find. This can be an Identifier node. ### Return value The found variable or `null`. ### Example ```js{8} const { findVariable } = require("eslint-utils") module.exports = { meta: {}, create(context) { return { Identifier(node) { const variable = findVariable(context.getScope(), node) }, } }, } ``` ## getInnermostScope ```js const scope = utils.getInnermostScope(initialScope, node) ``` Get the innermost scope which contains a given node. ### Parameters Name | Type | Description :-----|:-----|:------------ initialScope | Scope | The scope to start finding. node | Node | The node to find the innermost scope. ### Return value The innermost scope which contains the given node. If such scope doesn't exist then it returns the 1st argument `initialScope`. ### Example ```js{9} const { getInnermostScope } = require("eslint-utils") module.exports = { meta: {}, create(context) { return { "Program"(node) { const globalScope = context.getScope() const maybeNodejsScope = getInnermostScope(globalScope, node) }, } }, } ``` ## ReferenceTracker class ```js const tracker = new utils.ReferenceTracker(globalScope, options) ``` The tracker for references. This provides reference tracking for global variables, CommonJS modules, and ES modules. ### Parameters Name | Type | Description :-----|:-----|:------------ globalScope | Scope | The global scope. options.mode | `"strict"` or `"legacy"` | The mode which determines how the `tracker.iterateEsmReferences()` method scans CommonJS modules. If this is `"strict"`, the method binds CommonJS modules to the default export. Otherwise, the method binds CommonJS modules to both the default export and named exports. Optional. Default is `"strict"`. options.globalObjectNames | string[] | The name list of Global Object. Optional. Default is `["global", "globalThis", "self", "window"]`. ## tracker.iterateGlobalReferences ```js const it = tracker.iterateGlobalReferences(traceMap) ``` Iterate the references that the given `traceMap` determined. This method starts to search from global variables. ### Parameters Name | Type | Description :-----|:-----|:------------ traceMap | object | The object which determines global variables and properties it iterates. ### Return value The Iterator which iterates the reference of global variables. Every reference is the object that has the following properties. Name | Type | Description :-----|:-----|:------------ node | Node | The node of the reference. path | string[] | The path of the reference. For example, if it's the access of `console.log` then `["console", "log"]`. type | symbol | The reference type. If this is `ReferenceTracker.READ` then it read the variable (or property). If this is `ReferenceTracker.CALL` then it called the variable (or property). If this is `ReferenceTracker.CONSTRUCT` then it called the variable (or property) with the `new` operator. entry | any | The property value of any of `ReferenceTracker.READ`, `ReferenceTracker.CALL`, and `ReferenceTracker.CONSTRUCT`. ### Examples ```js const { ReferenceTracker } = require("eslint-utils") module.exports = { meta: {}, create(context) { return { "Program:exit"() { const tracker = new ReferenceTracker(context.getScope()) const traceMap = { // Find `console.log`, `console.info`, `console.warn`, and `console.error`. console: { log: { [ReferenceTracker.READ]: true }, info: { [ReferenceTracker.READ]: true }, warn: { [ReferenceTracker.READ]: true }, error: { [ReferenceTracker.READ]: true }, }, // Find `Buffer()` and `new Buffer()`. Buffer: { [ReferenceTracker.CALL]: true, [ReferenceTracker.CONSTRUCT]: true, }, } for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) { context.report({ node, message: "disallow {{name}}.", data: { name: path.join(".") }, }) } }, } }, } ``` ## tracker.iterateCjsReferences ```js const it = tracker.iterateCjsReferences(traceMap) ``` Iterate the references that the given `traceMap` determined. This method starts to search from `require()` expression. ### Parameters Name | Type | Description :-----|:-----|:------------ traceMap | object | The object which determines modules it iterates. ### Return value The Iterator which iterates the reference of modules. Every reference is the object that has the following properties. Name | Type | Description :-----|:-----|:------------ node | Node | The node of the reference. path | string[] | The path of the reference. For example, if it's the access of `fs.exists` then `["fs", "exists"]`. type | symbol | The reference type. If this is `ReferenceTracker.READ` then it read the variable (or property). If this is `ReferenceTracker.CALL` then it called the variable (or property). If this is `ReferenceTracker.CONSTRUCT` then it called the variable (or property) with the `new` operator. entry | any | The property value of any of `ReferenceTracker.READ`, `ReferenceTracker.CALL`, and `ReferenceTracker.CONSTRUCT`. ### Examples ```js const { ReferenceTracker } = require("eslint-utils") module.exports = { meta: {}, create(context) { return { "Program:exit"() { const tracker = new ReferenceTracker(context.getScope()) const traceMap = { // Find `Buffer()` and `new Buffer()` of `buffer` module. buffer: { Buffer: { [ReferenceTracker.CALL]: true, [ReferenceTracker.CONSTRUCT]: true, }, }, // Find `exists` of `fs` module. fs: { exists: { [ReferenceTracker.READ]: true, }, }, } for (const { node, path } of tracker.iterateCjsReferences(traceMap)) { context.report({ node, message: "disallow {{name}}.", data: { name: path.join(".") }, }) } }, } }, } ``` ## tracker.iterateEsmReferences ```js const it = tracker.iterateEsmReferences(traceMap) ``` Iterate the references that the given `traceMap` determined. This method starts to search from `import`/`export` declarations. ### Parameters Name | Type | Description :-----|:-----|:------------ traceMap | object | The object which determines modules it iterates. ### Return value The Iterator which iterates the reference of modules. Every reference is the object that has the following properties. Name | Type | Description :-----|:-----|:------------ node | Node | The node of the reference. path | string[] | The path of the reference. For example, if it's the access of `fs.exists` then `["fs", "exists"]`. type | symbol | The reference type. If this is `ReferenceTracker.READ` then it read the variable (or property). If this is `ReferenceTracker.CALL` then it called the variable (or property). If this is `ReferenceTracker.CONSTRUCT` then it called the variable (or property) with the `new` operator. entry | any | The property value of any of `ReferenceTracker.READ`, `ReferenceTracker.CALL`, and `ReferenceTracker.CONSTRUCT`. ### Examples ```js const { ReferenceTracker } = require("eslint-utils") module.exports = { meta: {}, create(context) { return { "Program:exit"() { const tracker = new ReferenceTracker(context.getScope()) const traceMap = { // Find `Buffer()` and `new Buffer()` of `buffer` module. buffer: { Buffer: { [ReferenceTracker.CALL]: true, [ReferenceTracker.CONSTRUCT]: true, }, }, // Find `exists` of `fs` module. fs: { exists: { [ReferenceTracker.READ]: true, }, }, } for (const { node, path } of tracker.iterateEsmReferences(traceMap)) { context.report({ node, message: "disallow {{name}}.", data: { name: path.join(".") }, }) } }, } }, } ``` eslint-utils-2.1.0/docs/api/token-utils.md000066400000000000000000000043511367166352100204640ustar00rootroot00000000000000# Token utilities ## isArrowToken / isNotArrowToken ```js utils.isArrowToken(token) utils.isNotArrowToken(token) ``` Check whether a given token is a `=>` token or not. ### Examples ```js{10} const { isArrowToken } = require("eslint-utils") module.exports = { meta: {}, create(context) { const sourceCode = context.getSourceCode() return { ArrowFunctionExpression(node) { const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken) }, } }, } ``` ## isClosingBraceToken / isNotClosingBraceToken ```js utils.isClosingBraceToken(token) utils.isNotClosingBraceToken(token) ``` Check whether a given token is a `}` token or not. ## isClosingBracketToken / isNotClosingBracketToken ```js utils.isClosingBracketToken(token) utils.isNotClosingBracketToken(token) ``` Check whether a given token is a `]` token or not. ## isClosingParenToken / isNotClosingParenToken ```js utils.isClosingParenToken(token) utils.isNotClosingParenToken(token) ``` Check whether a given token is a `)` token or not. ## isColonToken / isNotColonToken ```js utils.isColonToken(token) utils.isNotColonToken(token) ``` Check whether a given token is a `:` token or not. ## isCommaToken / isNotCommaToken ```js utils.isCommaToken(token) utils.isNotCommaToken(token) ``` Check whether a given token is a `,` token or not. ## isCommentToken / isNotCommentToken ```js utils.isCommentToken(token) utils.isNotCommentToken(token) ``` Check whether a given token is a comment token or not. ## isOpeningBraceToken / isNotOpeningBraceToken ```js utils.isOpeningBraceToken(token) utils.isNotOpeningBraceToken(token) ``` Check whether a given token is a `{` token or not. ## isOpeningBracketToken / isNotOpeningBracketToken ```js utils.isOpeningBracketToken(token) utils.isNotOpeningBracketToken(token) ``` Check whether a given token is a `[` token or not. ## isOpeningParenToken / isNotOpeningParenToken ```js utils.isOpeningParenToken(token) utils.isNotOpeningParenToken(token) ``` Check whether a given token is a `(` token or not. ## isSemicolonToken / isNotSemicolonToken ```js utils.isSemicolonToken(token) utils.isNotSemicolonToken(token) ``` Check whether a given token is a `;` token or not. eslint-utils-2.1.0/package.json000066400000000000000000000034231367166352100164500ustar00rootroot00000000000000{ "name": "eslint-utils", "version": "2.1.0", "description": "Utilities for ESLint plugins.", "engines": { "node": ">=6" }, "sideEffects": false, "main": "index", "module": "index.mjs", "files": [ "index.*" ], "dependencies": { "eslint-visitor-keys": "^1.1.0" }, "devDependencies": { "@mysticatea/eslint-plugin": "^12.0.0", "codecov": "^3.6.1", "dot-prop": "^4.2.0", "eslint": "^6.5.1", "esm": "^3.2.25", "espree": "^6.1.1", "mocha": "^6.2.2", "npm-run-all": "^4.1.5", "nyc": "^14.1.1", "opener": "^1.5.1", "rimraf": "^3.0.0", "rollup": "^1.25.0", "rollup-plugin-sourcemaps": "^0.4.2", "semver": "^7.3.2", "vuepress": "^1.2.0", "warun": "^1.0.0" }, "scripts": { "prebuild": "npm run -s clean", "build": "rollup -c", "clean": "rimraf .nyc_output coverage index.*", "codecov": "nyc report -r lcovonly && codecov", "coverage": "opener ./coverage/lcov-report/index.html", "docs:build": "vuepress build docs", "docs:watch": "vuepress dev docs", "lint": "eslint src test", "test": "run-s lint build test:mocha", "test:mocha": "nyc mocha --reporter dot \"test/*.js\"", "preversion": "npm test && npm run -s build", "postversion": "git push && git push --tags", "prewatch": "npm run -s clean", "watch": "warun \"{src,test}/**/*.js\" -- npm run -s test:mocha" }, "repository": { "type": "git", "url": "git+https://github.com/mysticatea/eslint-utils.git" }, "keywords": [ "eslint" ], "author": "Toru Nagashima", "license": "MIT", "bugs": { "url": "https://github.com/mysticatea/eslint-utils/issues" }, "homepage": "https://github.com/mysticatea/eslint-utils#readme", "funding": "https://github.com/sponsors/mysticatea" } eslint-utils-2.1.0/rollup.config.js000066400000000000000000000013671367166352100173060ustar00rootroot00000000000000/** * @author Toru Nagashima * See LICENSE file in root directory for full license. */ import sourcemaps from "rollup-plugin-sourcemaps" /** * Define the output configuration. * @param {string} ext The extension for generated files. * @returns {object} The output configuration */ function config(ext) { return { input: "src/index.js", output: { file: `index${ext}`, format: ext === ".mjs" ? "es" : "cjs", sourcemap: true, banner: "/*! @author Toru Nagashima */", }, plugins: [sourcemaps()], external: Object.keys(require("./package.json").dependencies), } } export default [config(".js"), config(".mjs")] eslint-utils-2.1.0/src/000077500000000000000000000000001367166352100147475ustar00rootroot00000000000000eslint-utils-2.1.0/src/find-variable.js000066400000000000000000000014621367166352100200130ustar00rootroot00000000000000import { getInnermostScope } from "./get-innermost-scope" /** * Find the variable of a given name. * @param {Scope} initialScope The scope to start finding. * @param {string|Node} nameOrNode The variable name to find. If this is a Node object then it should be an Identifier node. * @returns {Variable|null} The found variable or null. */ export function findVariable(initialScope, nameOrNode) { let name = "" let scope = initialScope if (typeof nameOrNode === "string") { name = nameOrNode } else { name = nameOrNode.name scope = getInnermostScope(scope, nameOrNode) } while (scope != null) { const variable = scope.set.get(name) if (variable != null) { return variable } scope = scope.upper } return null } eslint-utils-2.1.0/src/get-function-head-location.js000066400000000000000000000027351367166352100224230ustar00rootroot00000000000000import { isArrowToken, isOpeningParenToken } from "./token-predicate" /** * Get the `(` token of the given function node. * @param {Node} node - The function node to get. * @param {SourceCode} sourceCode - The source code object to get tokens. * @returns {Token} `(` token. */ function getOpeningParenOfParams(node, sourceCode) { return node.id ? sourceCode.getTokenAfter(node.id, isOpeningParenToken) : sourceCode.getFirstToken(node, isOpeningParenToken) } /** * Get the location of the given function node for reporting. * @param {Node} node - The function node to get. * @param {SourceCode} sourceCode - The source code object to get tokens. * @returns {string} The location of the function node for reporting. */ export function getFunctionHeadLocation(node, sourceCode) { const parent = node.parent let start = null let end = null if (node.type === "ArrowFunctionExpression") { const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken) start = arrowToken.loc.start end = arrowToken.loc.end } else if ( parent.type === "Property" || parent.type === "MethodDefinition" ) { start = parent.loc.start end = getOpeningParenOfParams(node, sourceCode).loc.start } else { start = node.loc.start end = getOpeningParenOfParams(node, sourceCode).loc.start } return { start: Object.assign({}, start), end: Object.assign({}, end), } } eslint-utils-2.1.0/src/get-function-name-with-kind.js000066400000000000000000000034041367166352100225220ustar00rootroot00000000000000import { getPropertyName } from "./get-property-name" /** * Get the name and kind of the given function node. * @param {ASTNode} node - The function node to get. * @returns {string} The name and kind of the function node. */ export function getFunctionNameWithKind(node) { const parent = node.parent const tokens = [] if (parent.type === "MethodDefinition" && parent.static) { tokens.push("static") } if (node.async) { tokens.push("async") } if (node.generator) { tokens.push("generator") } if (node.type === "ArrowFunctionExpression") { tokens.push("arrow", "function") } else if ( parent.type === "Property" || parent.type === "MethodDefinition" ) { if (parent.kind === "constructor") { return "constructor" } if (parent.kind === "get") { tokens.push("getter") } else if (parent.kind === "set") { tokens.push("setter") } else { tokens.push("method") } } else { tokens.push("function") } if (node.id) { tokens.push(`'${node.id.name}'`) } else { const name = getPropertyName(parent) if (name) { tokens.push(`'${name}'`) } } if (node.type === "ArrowFunctionExpression") { if ( parent.type === "VariableDeclarator" && parent.id && parent.id.type === "Identifier" ) { tokens.push(`'${parent.id.name}'`) } if ( parent.type === "AssignmentExpression" && parent.left && parent.left.type === "Identifier" ) { tokens.push(`'${parent.left.name}'`) } } return tokens.join(" ") } eslint-utils-2.1.0/src/get-innermost-scope.js000066400000000000000000000013021367166352100212030ustar00rootroot00000000000000/** * Get the innermost scope which contains a given location. * @param {Scope} initialScope The initial scope to search. * @param {Node} node The location to search. * @returns {Scope} The innermost scope. */ export function getInnermostScope(initialScope, node) { const location = node.range[0] let scope = initialScope let found = false do { found = false for (const childScope of scope.childScopes) { const range = childScope.block.range if (range[0] <= location && location < range[1]) { scope = childScope found = true break } } } while (found) return scope } eslint-utils-2.1.0/src/get-property-name.js000066400000000000000000000021641367166352100206670ustar00rootroot00000000000000import { getStringIfConstant } from "./get-string-if-constant" /** * Get the property name from a MemberExpression node or a Property node. * @param {Node} node The node to get. * @param {Scope} [initialScope] The scope to start finding variable. Optional. If the node is a computed property node and this scope was given, this checks the computed property name by the `getStringIfConstant` function with the scope, and returns the value of it. * @returns {string|null} The property name of the node. */ export function getPropertyName(node, initialScope) { switch (node.type) { case "MemberExpression": if (node.computed) { return getStringIfConstant(node.property, initialScope) } return node.property.name case "Property": case "MethodDefinition": if (node.computed) { return getStringIfConstant(node.key, initialScope) } if (node.key.type === "Literal") { return String(node.key.value) } return node.key.name // no default } return null } eslint-utils-2.1.0/src/get-static-value.js000066400000000000000000000413401367166352100204650ustar00rootroot00000000000000/* globals BigInt, globalThis, global, self, window */ import { findVariable } from "./find-variable" const globalObject = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {} const builtinNames = Object.freeze( new Set([ "Array", "ArrayBuffer", "BigInt", "BigInt64Array", "BigUint64Array", "Boolean", "DataView", "Date", "decodeURI", "decodeURIComponent", "encodeURI", "encodeURIComponent", "escape", "Float32Array", "Float64Array", "Function", "Infinity", "Int16Array", "Int32Array", "Int8Array", "isFinite", "isNaN", "isPrototypeOf", "JSON", "Map", "Math", "NaN", "Number", "Object", "parseFloat", "parseInt", "Promise", "Proxy", "Reflect", "RegExp", "Set", "String", "Symbol", "Uint16Array", "Uint32Array", "Uint8Array", "Uint8ClampedArray", "undefined", "unescape", "WeakMap", "WeakSet", ]) ) const callAllowed = new Set( [ Array.isArray, typeof BigInt === "function" ? BigInt : undefined, Boolean, Date, Date.parse, decodeURI, decodeURIComponent, encodeURI, encodeURIComponent, escape, isFinite, isNaN, isPrototypeOf, ...Object.getOwnPropertyNames(Math) .map(k => Math[k]) .filter(f => typeof f === "function"), Number, Number.isFinite, Number.isNaN, Number.parseFloat, Number.parseInt, Object, Object.entries, Object.is, Object.isExtensible, Object.isFrozen, Object.isSealed, Object.keys, Object.values, parseFloat, parseInt, RegExp, String, String.fromCharCode, String.fromCodePoint, String.raw, Symbol, Symbol.for, Symbol.keyFor, unescape, ].filter(f => typeof f === "function") ) const callPassThrough = new Set([ Object.freeze, Object.preventExtensions, Object.seal, ]) /** * Get the property descriptor. * @param {object} object The object to get. * @param {string|number|symbol} name The property name to get. */ function getPropertyDescriptor(object, name) { let x = object while ((typeof x === "object" || typeof x === "function") && x !== null) { const d = Object.getOwnPropertyDescriptor(x, name) if (d) { return d } x = Object.getPrototypeOf(x) } return null } /** * Check if a property is getter or not. * @param {object} object The object to check. * @param {string|number|symbol} name The property name to check. */ function isGetter(object, name) { const d = getPropertyDescriptor(object, name) return d != null && d.get != null } /** * Get the element values of a given node list. * @param {Node[]} nodeList The node list to get values. * @param {Scope|undefined} initialScope The initial scope to find variables. * @returns {any[]|null} The value list if all nodes are constant. Otherwise, null. */ function getElementValues(nodeList, initialScope) { const valueList = [] for (let i = 0; i < nodeList.length; ++i) { const elementNode = nodeList[i] if (elementNode == null) { valueList.length = i + 1 } else if (elementNode.type === "SpreadElement") { const argument = getStaticValueR(elementNode.argument, initialScope) if (argument == null) { return null } valueList.push(...argument.value) } else { const element = getStaticValueR(elementNode, initialScope) if (element == null) { return null } valueList.push(element.value) } } return valueList } const operations = Object.freeze({ ArrayExpression(node, initialScope) { const elements = getElementValues(node.elements, initialScope) return elements != null ? { value: elements } : null }, AssignmentExpression(node, initialScope) { if (node.operator === "=") { return getStaticValueR(node.right, initialScope) } return null }, //eslint-disable-next-line complexity BinaryExpression(node, initialScope) { if (node.operator === "in" || node.operator === "instanceof") { // Not supported. return null } const left = getStaticValueR(node.left, initialScope) const right = getStaticValueR(node.right, initialScope) if (left != null && right != null) { switch (node.operator) { case "==": return { value: left.value == right.value } //eslint-disable-line eqeqeq case "!=": return { value: left.value != right.value } //eslint-disable-line eqeqeq case "===": return { value: left.value === right.value } case "!==": return { value: left.value !== right.value } case "<": return { value: left.value < right.value } case "<=": return { value: left.value <= right.value } case ">": return { value: left.value > right.value } case ">=": return { value: left.value >= right.value } case "<<": return { value: left.value << right.value } case ">>": return { value: left.value >> right.value } case ">>>": return { value: left.value >>> right.value } case "+": return { value: left.value + right.value } case "-": return { value: left.value - right.value } case "*": return { value: left.value * right.value } case "/": return { value: left.value / right.value } case "%": return { value: left.value % right.value } case "**": return { value: Math.pow(left.value, right.value) } case "|": return { value: left.value | right.value } case "^": return { value: left.value ^ right.value } case "&": return { value: left.value & right.value } // no default } } return null }, CallExpression(node, initialScope) { const calleeNode = node.callee const args = getElementValues(node.arguments, initialScope) if (args != null) { if (calleeNode.type === "MemberExpression") { const object = getStaticValueR(calleeNode.object, initialScope) if (object != null) { if ( object.value == null && (object.optional || node.optional) ) { return { value: undefined, optional: true } } const property = calleeNode.computed ? getStaticValueR(calleeNode.property, initialScope) : { value: calleeNode.property.name } if (property != null) { const receiver = object.value const methodName = property.value if (callAllowed.has(receiver[methodName])) { return { value: receiver[methodName](...args) } } if (callPassThrough.has(receiver[methodName])) { return { value: args[0] } } } } } else { const callee = getStaticValueR(calleeNode, initialScope) if (callee != null) { if (callee.value == null && node.optional) { return { value: undefined, optional: true } } const func = callee.value if (callAllowed.has(func)) { return { value: func(...args) } } if (callPassThrough.has(func)) { return { value: args[0] } } } } } return null }, ConditionalExpression(node, initialScope) { const test = getStaticValueR(node.test, initialScope) if (test != null) { return test.value ? getStaticValueR(node.consequent, initialScope) : getStaticValueR(node.alternate, initialScope) } return null }, ExpressionStatement(node, initialScope) { return getStaticValueR(node.expression, initialScope) }, Identifier(node, initialScope) { if (initialScope != null) { const variable = findVariable(initialScope, node) // Built-in globals. if ( variable != null && variable.defs.length === 0 && builtinNames.has(variable.name) && variable.name in globalObject ) { return { value: globalObject[variable.name] } } // Constants. if (variable != null && variable.defs.length === 1) { const def = variable.defs[0] if ( def.parent && def.parent.kind === "const" && // TODO(mysticatea): don't support destructuring here. def.node.id.type === "Identifier" ) { return getStaticValueR(def.node.init, initialScope) } } } return null }, Literal(node) { //istanbul ignore if : this is implementation-specific behavior. if ((node.regex != null || node.bigint != null) && node.value == null) { // It was a RegExp/BigInt literal, but Node.js didn't support it. return null } return { value: node.value } }, LogicalExpression(node, initialScope) { const left = getStaticValueR(node.left, initialScope) if (left != null) { if ( (node.operator === "||" && Boolean(left.value) === true) || (node.operator === "&&" && Boolean(left.value) === false) || (node.operator === "??" && left.value != null) ) { return left } const right = getStaticValueR(node.right, initialScope) if (right != null) { return right } } return null }, MemberExpression(node, initialScope) { const object = getStaticValueR(node.object, initialScope) if (object != null) { if (object.value == null && (object.optional || node.optional)) { return { value: undefined, optional: true } } const property = node.computed ? getStaticValueR(node.property, initialScope) : { value: node.property.name } if (property != null && !isGetter(object.value, property.value)) { return { value: object.value[property.value] } } } return null }, ChainExpression(node, initialScope) { const expression = getStaticValueR(node.expression, initialScope) if (expression != null) { return { value: expression.value } } return null }, NewExpression(node, initialScope) { const callee = getStaticValueR(node.callee, initialScope) const args = getElementValues(node.arguments, initialScope) if (callee != null && args != null) { const Func = callee.value if (callAllowed.has(Func)) { return { value: new Func(...args) } } } return null }, ObjectExpression(node, initialScope) { const object = {} for (const propertyNode of node.properties) { if (propertyNode.type === "Property") { if (propertyNode.kind !== "init") { return null } const key = propertyNode.computed ? getStaticValueR(propertyNode.key, initialScope) : { value: propertyNode.key.name } const value = getStaticValueR(propertyNode.value, initialScope) if (key == null || value == null) { return null } object[key.value] = value.value } else if ( propertyNode.type === "SpreadElement" || propertyNode.type === "ExperimentalSpreadProperty" ) { const argument = getStaticValueR( propertyNode.argument, initialScope ) if (argument == null) { return null } Object.assign(object, argument.value) } else { return null } } return { value: object } }, SequenceExpression(node, initialScope) { const last = node.expressions[node.expressions.length - 1] return getStaticValueR(last, initialScope) }, TaggedTemplateExpression(node, initialScope) { const tag = getStaticValueR(node.tag, initialScope) const expressions = getElementValues( node.quasi.expressions, initialScope ) if (tag != null && expressions != null) { const func = tag.value const strings = node.quasi.quasis.map(q => q.value.cooked) strings.raw = node.quasi.quasis.map(q => q.value.raw) if (func === String.raw) { return { value: func(strings, ...expressions) } } } return null }, TemplateLiteral(node, initialScope) { const expressions = getElementValues(node.expressions, initialScope) if (expressions != null) { let value = node.quasis[0].value.cooked for (let i = 0; i < expressions.length; ++i) { value += expressions[i] value += node.quasis[i + 1].value.cooked } return { value } } return null }, UnaryExpression(node, initialScope) { if (node.operator === "delete") { // Not supported. return null } if (node.operator === "void") { return { value: undefined } } const arg = getStaticValueR(node.argument, initialScope) if (arg != null) { switch (node.operator) { case "-": return { value: -arg.value } case "+": return { value: +arg.value } //eslint-disable-line no-implicit-coercion case "!": return { value: !arg.value } case "~": return { value: ~arg.value } case "typeof": return { value: typeof arg.value } // no default } } return null }, }) /** * Get the value of a given node if it's a static value. * @param {Node} node The node to get. * @param {Scope|undefined} initialScope The scope to start finding variable. * @returns {{value:any}|{value:undefined,optional?:true}|null} The static value of the node, or `null`. */ function getStaticValueR(node, initialScope) { if (node != null && Object.hasOwnProperty.call(operations, node.type)) { return operations[node.type](node, initialScope) } return null } /** * Get the value of a given node if it's a static value. * @param {Node} node The node to get. * @param {Scope} [initialScope] The scope to start finding variable. Optional. If this scope was given, this tries to resolve identifier references which are in the given node as much as possible. * @returns {{value:any}|{value:undefined,optional?:true}|null} The static value of the node, or `null`. */ export function getStaticValue(node, initialScope = null) { try { return getStaticValueR(node, initialScope) } catch (_error) { return null } } eslint-utils-2.1.0/src/get-string-if-constant.js000066400000000000000000000017011367166352100216120ustar00rootroot00000000000000import { getStaticValue } from "./get-static-value" /** * Get the value of a given node if it's a literal or a template literal. * @param {Node} node The node to get. * @param {Scope} [initialScope] The scope to start finding variable. Optional. If the node is an Identifier node and this scope was given, this checks the variable of the identifier, and returns the value of it if the variable is a constant. * @returns {string|null} The value of the node, or `null`. */ export function getStringIfConstant(node, initialScope = null) { // Handle the literals that the platform doesn't support natively. if (node && node.type === "Literal" && node.value === null) { if (node.regex) { return `/${node.regex.pattern}/${node.regex.flags}` } if (node.bigint) { return node.bigint } } const evaluated = getStaticValue(node, initialScope) return evaluated && String(evaluated.value) } eslint-utils-2.1.0/src/has-side-effect.js000066400000000000000000000124421367166352100202370ustar00rootroot00000000000000import evk from "eslint-visitor-keys" const typeConversionBinaryOps = Object.freeze( new Set([ "==", "!=", "<", "<=", ">", ">=", "<<", ">>", ">>>", "+", "-", "*", "/", "%", "|", "^", "&", "in", ]) ) const typeConversionUnaryOps = Object.freeze(new Set(["-", "+", "!", "~"])) /** * Check whether the given value is an ASTNode or not. * @param {any} x The value to check. * @returns {boolean} `true` if the value is an ASTNode. */ function isNode(x) { return x !== null && typeof x === "object" && typeof x.type === "string" } const visitor = Object.freeze( Object.assign(Object.create(null), { $visit(node, options, visitorKeys) { const { type } = node if (typeof this[type] === "function") { return this[type](node, options, visitorKeys) } return this.$visitChildren(node, options, visitorKeys) }, $visitChildren(node, options, visitorKeys) { const { type } = node for (const key of visitorKeys[type] || evk.getKeys(node)) { const value = node[key] if (Array.isArray(value)) { for (const element of value) { if ( isNode(element) && this.$visit(element, options, visitorKeys) ) { return true } } } else if ( isNode(value) && this.$visit(value, options, visitorKeys) ) { return true } } return false }, ArrowFunctionExpression() { return false }, AssignmentExpression() { return true }, AwaitExpression() { return true }, BinaryExpression(node, options, visitorKeys) { if ( options.considerImplicitTypeConversion && typeConversionBinaryOps.has(node.operator) && (node.left.type !== "Literal" || node.right.type !== "Literal") ) { return true } return this.$visitChildren(node, options, visitorKeys) }, CallExpression() { return true }, FunctionExpression() { return false }, ImportExpression() { return true }, MemberExpression(node, options, visitorKeys) { if (options.considerGetters) { return true } if ( options.considerImplicitTypeConversion && node.computed && node.property.type !== "Literal" ) { return true } return this.$visitChildren(node, options, visitorKeys) }, MethodDefinition(node, options, visitorKeys) { if ( options.considerImplicitTypeConversion && node.computed && node.key.type !== "Literal" ) { return true } return this.$visitChildren(node, options, visitorKeys) }, NewExpression() { return true }, Property(node, options, visitorKeys) { if ( options.considerImplicitTypeConversion && node.computed && node.key.type !== "Literal" ) { return true } return this.$visitChildren(node, options, visitorKeys) }, UnaryExpression(node, options, visitorKeys) { if (node.operator === "delete") { return true } if ( options.considerImplicitTypeConversion && typeConversionUnaryOps.has(node.operator) && node.argument.type !== "Literal" ) { return true } return this.$visitChildren(node, options, visitorKeys) }, UpdateExpression() { return true }, YieldExpression() { return true }, }) ) /** * Check whether a given node has any side effect or not. * @param {Node} node The node to get. * @param {SourceCode} sourceCode The source code object. * @param {object} [options] The option object. * @param {boolean} [options.considerGetters=false] If `true` then it considers member accesses as the node which has side effects. * @param {boolean} [options.considerImplicitTypeConversion=false] If `true` then it considers implicit type conversion as the node which has side effects. * @param {object} [options.visitorKeys=evk.KEYS] The keys to traverse nodes. Use `context.getSourceCode().visitorKeys`. * @returns {boolean} `true` if the node has a certain side effect. */ export function hasSideEffect( node, sourceCode, { considerGetters = false, considerImplicitTypeConversion = false } = {} ) { return visitor.$visit( node, { considerGetters, considerImplicitTypeConversion }, sourceCode.visitorKeys || evk.KEYS ) } eslint-utils-2.1.0/src/index.js000066400000000000000000000055531367166352100164240ustar00rootroot00000000000000import { findVariable } from "./find-variable" import { getFunctionHeadLocation } from "./get-function-head-location" import { getFunctionNameWithKind } from "./get-function-name-with-kind" import { getInnermostScope } from "./get-innermost-scope" import { getPropertyName } from "./get-property-name" import { getStaticValue } from "./get-static-value" import { getStringIfConstant } from "./get-string-if-constant" import { hasSideEffect } from "./has-side-effect" import { isParenthesized } from "./is-parenthesized" import { PatternMatcher } from "./pattern-matcher" import { CALL, CONSTRUCT, ESM, READ, ReferenceTracker, } from "./reference-tracker" import { isArrowToken, isClosingBraceToken, isClosingBracketToken, isClosingParenToken, isColonToken, isCommaToken, isCommentToken, isNotArrowToken, isNotClosingBraceToken, isNotClosingBracketToken, isNotClosingParenToken, isNotColonToken, isNotCommaToken, isNotCommentToken, isNotOpeningBraceToken, isNotOpeningBracketToken, isNotOpeningParenToken, isNotSemicolonToken, isOpeningBraceToken, isOpeningBracketToken, isOpeningParenToken, isSemicolonToken, } from "./token-predicate" export default { CALL, CONSTRUCT, ESM, findVariable, getFunctionHeadLocation, getFunctionNameWithKind, getInnermostScope, getPropertyName, getStaticValue, getStringIfConstant, hasSideEffect, isArrowToken, isClosingBraceToken, isClosingBracketToken, isClosingParenToken, isColonToken, isCommaToken, isCommentToken, isNotArrowToken, isNotClosingBraceToken, isNotClosingBracketToken, isNotClosingParenToken, isNotColonToken, isNotCommaToken, isNotCommentToken, isNotOpeningBraceToken, isNotOpeningBracketToken, isNotOpeningParenToken, isNotSemicolonToken, isOpeningBraceToken, isOpeningBracketToken, isOpeningParenToken, isParenthesized, isSemicolonToken, PatternMatcher, READ, ReferenceTracker, } export { CALL, CONSTRUCT, ESM, findVariable, getFunctionHeadLocation, getFunctionNameWithKind, getInnermostScope, getPropertyName, getStaticValue, getStringIfConstant, hasSideEffect, isArrowToken, isClosingBraceToken, isClosingBracketToken, isClosingParenToken, isColonToken, isCommaToken, isCommentToken, isNotArrowToken, isNotClosingBraceToken, isNotClosingBracketToken, isNotClosingParenToken, isNotColonToken, isNotCommaToken, isNotCommentToken, isNotOpeningBraceToken, isNotOpeningBracketToken, isNotOpeningParenToken, isNotSemicolonToken, isOpeningBraceToken, isOpeningBracketToken, isOpeningParenToken, isParenthesized, isSemicolonToken, PatternMatcher, READ, ReferenceTracker, } eslint-utils-2.1.0/src/is-parenthesized.js000066400000000000000000000066451367166352100205760ustar00rootroot00000000000000import { isClosingParenToken, isOpeningParenToken } from "./token-predicate" /** * Get the left parenthesis of the parent node syntax if it exists. * E.g., `if (a) {}` then the `(`. * @param {Node} node The AST node to check. * @param {SourceCode} sourceCode The source code object to get tokens. * @returns {Token|null} The left parenthesis of the parent node syntax */ function getParentSyntaxParen(node, sourceCode) { const parent = node.parent switch (parent.type) { case "CallExpression": case "NewExpression": if (parent.arguments.length === 1 && parent.arguments[0] === node) { return sourceCode.getTokenAfter( parent.callee, isOpeningParenToken ) } return null case "DoWhileStatement": if (parent.test === node) { return sourceCode.getTokenAfter( parent.body, isOpeningParenToken ) } return null case "IfStatement": case "WhileStatement": if (parent.test === node) { return sourceCode.getFirstToken(parent, 1) } return null case "ImportExpression": if (parent.source === node) { return sourceCode.getFirstToken(parent, 1) } return null case "SwitchStatement": if (parent.discriminant === node) { return sourceCode.getFirstToken(parent, 1) } return null case "WithStatement": if (parent.object === node) { return sourceCode.getFirstToken(parent, 1) } return null default: return null } } /** * Check whether a given node is parenthesized or not. * @param {number} times The number of parantheses. * @param {Node} node The AST node to check. * @param {SourceCode} sourceCode The source code object to get tokens. * @returns {boolean} `true` if the node is parenthesized the given times. */ /** * Check whether a given node is parenthesized or not. * @param {Node} node The AST node to check. * @param {SourceCode} sourceCode The source code object to get tokens. * @returns {boolean} `true` if the node is parenthesized. */ export function isParenthesized( timesOrNode, nodeOrSourceCode, optionalSourceCode ) { let times, node, sourceCode, maybeLeftParen, maybeRightParen if (typeof timesOrNode === "number") { times = timesOrNode | 0 node = nodeOrSourceCode sourceCode = optionalSourceCode if (!(times >= 1)) { throw new TypeError("'times' should be a positive integer.") } } else { times = 1 node = timesOrNode sourceCode = nodeOrSourceCode } if (node == null) { return false } maybeLeftParen = maybeRightParen = node do { maybeLeftParen = sourceCode.getTokenBefore(maybeLeftParen) maybeRightParen = sourceCode.getTokenAfter(maybeRightParen) } while ( maybeLeftParen != null && maybeRightParen != null && isOpeningParenToken(maybeLeftParen) && isClosingParenToken(maybeRightParen) && // Avoid false positive such as `if (a) {}` maybeLeftParen !== getParentSyntaxParen(node, sourceCode) && --times > 0 ) return times === 0 } eslint-utils-2.1.0/src/pattern-matcher.js000066400000000000000000000113641367166352100204100ustar00rootroot00000000000000/** * @author Toru Nagashima * See LICENSE file in root directory for full license. */ const placeholder = /\$(?:[$&`']|[1-9][0-9]?)/gu /** @type {WeakMap} */ const internal = new WeakMap() /** * Check whether a given character is escaped or not. * @param {string} str The string to check. * @param {number} index The location of the character to check. * @returns {boolean} `true` if the character is escaped. */ function isEscaped(str, index) { let escaped = false for (let i = index - 1; i >= 0 && str.charCodeAt(i) === 0x5c; --i) { escaped = !escaped } return escaped } /** * Replace a given string by a given matcher. * @param {PatternMatcher} matcher The pattern matcher. * @param {string} str The string to be replaced. * @param {string} replacement The new substring to replace each matched part. * @returns {string} The replaced string. */ function replaceS(matcher, str, replacement) { const chunks = [] let index = 0 /** @type {RegExpExecArray} */ let match = null /** * @param {string} key The placeholder. * @returns {string} The replaced string. */ function replacer(key) { switch (key) { case "$$": return "$" case "$&": return match[0] case "$`": return str.slice(0, match.index) case "$'": return str.slice(match.index + match[0].length) default: { const i = key.slice(1) if (i in match) { return match[i] } return key } } } for (match of matcher.execAll(str)) { chunks.push(str.slice(index, match.index)) chunks.push(replacement.replace(placeholder, replacer)) index = match.index + match[0].length } chunks.push(str.slice(index)) return chunks.join("") } /** * Replace a given string by a given matcher. * @param {PatternMatcher} matcher The pattern matcher. * @param {string} str The string to be replaced. * @param {(...strs[])=>string} replace The function to replace each matched part. * @returns {string} The replaced string. */ function replaceF(matcher, str, replace) { const chunks = [] let index = 0 for (const match of matcher.execAll(str)) { chunks.push(str.slice(index, match.index)) chunks.push(String(replace(...match, match.index, match.input))) index = match.index + match[0].length } chunks.push(str.slice(index)) return chunks.join("") } /** * The class to find patterns as considering escape sequences. */ export class PatternMatcher { /** * Initialize this matcher. * @param {RegExp} pattern The pattern to match. * @param {{escaped:boolean}} options The options. */ constructor(pattern, { escaped = false } = {}) { if (!(pattern instanceof RegExp)) { throw new TypeError("'pattern' should be a RegExp instance.") } if (!pattern.flags.includes("g")) { throw new Error("'pattern' should contains 'g' flag.") } internal.set(this, { pattern: new RegExp(pattern.source, pattern.flags), escaped: Boolean(escaped), }) } /** * Find the pattern in a given string. * @param {string} str The string to find. * @returns {IterableIterator} The iterator which iterate the matched information. */ *execAll(str) { const { pattern, escaped } = internal.get(this) let match = null let lastIndex = 0 pattern.lastIndex = 0 while ((match = pattern.exec(str)) != null) { if (escaped || !isEscaped(str, match.index)) { lastIndex = pattern.lastIndex yield match pattern.lastIndex = lastIndex } } } /** * Check whether the pattern is found in a given string. * @param {string} str The string to check. * @returns {boolean} `true` if the pattern was found in the string. */ test(str) { const it = this.execAll(str) const ret = it.next() return !ret.done } /** * Replace a given string. * @param {string} str The string to be replaced. * @param {(string|((...strs:string[])=>string))} replacer The string or function to replace. This is the same as the 2nd argument of `String.prototype.replace`. * @returns {string} The replaced string. */ [Symbol.replace](str, replacer) { return typeof replacer === "function" ? replaceF(this, String(str), replacer) : replaceS(this, String(str), String(replacer)) } } eslint-utils-2.1.0/src/reference-tracker.js000066400000000000000000000357321367166352100207060ustar00rootroot00000000000000import { findVariable } from "./find-variable" import { getPropertyName } from "./get-property-name" import { getStringIfConstant } from "./get-string-if-constant" const IMPORT_TYPE = /^(?:Import|Export(?:All|Default|Named))Declaration$/u const has = Function.call.bind(Object.hasOwnProperty) export const READ = Symbol("read") export const CALL = Symbol("call") export const CONSTRUCT = Symbol("construct") export const ESM = Symbol("esm") const requireCall = { require: { [CALL]: true } } /** * Check whether a given variable is modified or not. * @param {Variable} variable The variable to check. * @returns {boolean} `true` if the variable is modified. */ function isModifiedGlobal(variable) { return ( variable == null || variable.defs.length !== 0 || variable.references.some(r => r.isWrite()) ) } /** * Check if the value of a given node is passed through to the parent syntax as-is. * For example, `a` and `b` in (`a || b` and `c ? a : b`) are passed through. * @param {Node} node A node to check. * @returns {boolean} `true` if the node is passed through. */ function isPassThrough(node) { const parent = node.parent switch (parent && parent.type) { case "ConditionalExpression": return parent.consequent === node || parent.alternate === node case "LogicalExpression": return true case "SequenceExpression": return parent.expressions[parent.expressions.length - 1] === node case "ChainExpression": return true default: return false } } /** * The reference tracker. */ export class ReferenceTracker { /** * Initialize this tracker. * @param {Scope} globalScope The global scope. * @param {object} [options] The options. * @param {"legacy"|"strict"} [options.mode="strict"] The mode to determine the ImportDeclaration's behavior for CJS modules. * @param {string[]} [options.globalObjectNames=["global","globalThis","self","window"]] The variable names for Global Object. */ constructor( globalScope, { mode = "strict", globalObjectNames = ["global", "globalThis", "self", "window"], } = {} ) { this.variableStack = [] this.globalScope = globalScope this.mode = mode this.globalObjectNames = globalObjectNames.slice(0) } /** * Iterate the references of global variables. * @param {object} traceMap The trace map. * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references. */ *iterateGlobalReferences(traceMap) { for (const key of Object.keys(traceMap)) { const nextTraceMap = traceMap[key] const path = [key] const variable = this.globalScope.set.get(key) if (isModifiedGlobal(variable)) { continue } yield* this._iterateVariableReferences( variable, path, nextTraceMap, true ) } for (const key of this.globalObjectNames) { const path = [] const variable = this.globalScope.set.get(key) if (isModifiedGlobal(variable)) { continue } yield* this._iterateVariableReferences( variable, path, traceMap, false ) } } /** * Iterate the references of CommonJS modules. * @param {object} traceMap The trace map. * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references. */ *iterateCjsReferences(traceMap) { for (const { node } of this.iterateGlobalReferences(requireCall)) { const key = getStringIfConstant(node.arguments[0]) if (key == null || !has(traceMap, key)) { continue } const nextTraceMap = traceMap[key] const path = [key] if (nextTraceMap[READ]) { yield { node, path, type: READ, info: nextTraceMap[READ], } } yield* this._iteratePropertyReferences(node, path, nextTraceMap) } } /** * Iterate the references of ES modules. * @param {object} traceMap The trace map. * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references. */ *iterateEsmReferences(traceMap) { const programNode = this.globalScope.block for (const node of programNode.body) { if (!IMPORT_TYPE.test(node.type) || node.source == null) { continue } const moduleId = node.source.value if (!has(traceMap, moduleId)) { continue } const nextTraceMap = traceMap[moduleId] const path = [moduleId] if (nextTraceMap[READ]) { yield { node, path, type: READ, info: nextTraceMap[READ] } } if (node.type === "ExportAllDeclaration") { for (const key of Object.keys(nextTraceMap)) { const exportTraceMap = nextTraceMap[key] if (exportTraceMap[READ]) { yield { node, path: path.concat(key), type: READ, info: exportTraceMap[READ], } } } } else { for (const specifier of node.specifiers) { const esm = has(nextTraceMap, ESM) const it = this._iterateImportReferences( specifier, path, esm ? nextTraceMap : this.mode === "legacy" ? Object.assign( { default: nextTraceMap }, nextTraceMap ) : { default: nextTraceMap } ) if (esm) { yield* it } else { for (const report of it) { report.path = report.path.filter(exceptDefault) if ( report.path.length >= 2 || report.type !== READ ) { yield report } } } } } } } /** * Iterate the references for a given variable. * @param {Variable} variable The variable to iterate that references. * @param {string[]} path The current path. * @param {object} traceMap The trace map. * @param {boolean} shouldReport = The flag to report those references. * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references. */ *_iterateVariableReferences(variable, path, traceMap, shouldReport) { if (this.variableStack.includes(variable)) { return } this.variableStack.push(variable) try { for (const reference of variable.references) { if (!reference.isRead()) { continue } const node = reference.identifier if (shouldReport && traceMap[READ]) { yield { node, path, type: READ, info: traceMap[READ] } } yield* this._iteratePropertyReferences(node, path, traceMap) } } finally { this.variableStack.pop() } } /** * Iterate the references for a given AST node. * @param rootNode The AST node to iterate references. * @param {string[]} path The current path. * @param {object} traceMap The trace map. * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references. */ //eslint-disable-next-line complexity *_iteratePropertyReferences(rootNode, path, traceMap) { let node = rootNode while (isPassThrough(node)) { node = node.parent } const parent = node.parent if (parent.type === "MemberExpression") { if (parent.object === node) { const key = getPropertyName(parent) if (key == null || !has(traceMap, key)) { return } path = path.concat(key) //eslint-disable-line no-param-reassign const nextTraceMap = traceMap[key] if (nextTraceMap[READ]) { yield { node: parent, path, type: READ, info: nextTraceMap[READ], } } yield* this._iteratePropertyReferences( parent, path, nextTraceMap ) } return } if (parent.type === "CallExpression") { if (parent.callee === node && traceMap[CALL]) { yield { node: parent, path, type: CALL, info: traceMap[CALL] } } return } if (parent.type === "NewExpression") { if (parent.callee === node && traceMap[CONSTRUCT]) { yield { node: parent, path, type: CONSTRUCT, info: traceMap[CONSTRUCT], } } return } if (parent.type === "AssignmentExpression") { if (parent.right === node) { yield* this._iterateLhsReferences(parent.left, path, traceMap) yield* this._iteratePropertyReferences(parent, path, traceMap) } return } if (parent.type === "AssignmentPattern") { if (parent.right === node) { yield* this._iterateLhsReferences(parent.left, path, traceMap) } return } if (parent.type === "VariableDeclarator") { if (parent.init === node) { yield* this._iterateLhsReferences(parent.id, path, traceMap) } } } /** * Iterate the references for a given Pattern node. * @param {Node} patternNode The Pattern node to iterate references. * @param {string[]} path The current path. * @param {object} traceMap The trace map. * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references. */ *_iterateLhsReferences(patternNode, path, traceMap) { if (patternNode.type === "Identifier") { const variable = findVariable(this.globalScope, patternNode) if (variable != null) { yield* this._iterateVariableReferences( variable, path, traceMap, false ) } return } if (patternNode.type === "ObjectPattern") { for (const property of patternNode.properties) { const key = getPropertyName(property) if (key == null || !has(traceMap, key)) { continue } const nextPath = path.concat(key) const nextTraceMap = traceMap[key] if (nextTraceMap[READ]) { yield { node: property, path: nextPath, type: READ, info: nextTraceMap[READ], } } yield* this._iterateLhsReferences( property.value, nextPath, nextTraceMap ) } return } if (patternNode.type === "AssignmentPattern") { yield* this._iterateLhsReferences(patternNode.left, path, traceMap) } } /** * Iterate the references for a given ModuleSpecifier node. * @param {Node} specifierNode The ModuleSpecifier node to iterate references. * @param {string[]} path The current path. * @param {object} traceMap The trace map. * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references. */ *_iterateImportReferences(specifierNode, path, traceMap) { const type = specifierNode.type if (type === "ImportSpecifier" || type === "ImportDefaultSpecifier") { const key = type === "ImportDefaultSpecifier" ? "default" : specifierNode.imported.name if (!has(traceMap, key)) { return } path = path.concat(key) //eslint-disable-line no-param-reassign const nextTraceMap = traceMap[key] if (nextTraceMap[READ]) { yield { node: specifierNode, path, type: READ, info: nextTraceMap[READ], } } yield* this._iterateVariableReferences( findVariable(this.globalScope, specifierNode.local), path, nextTraceMap, false ) return } if (type === "ImportNamespaceSpecifier") { yield* this._iterateVariableReferences( findVariable(this.globalScope, specifierNode.local), path, traceMap, false ) return } if (type === "ExportSpecifier") { const key = specifierNode.local.name if (!has(traceMap, key)) { return } path = path.concat(key) //eslint-disable-line no-param-reassign const nextTraceMap = traceMap[key] if (nextTraceMap[READ]) { yield { node: specifierNode, path, type: READ, info: nextTraceMap[READ], } } } } } ReferenceTracker.READ = READ ReferenceTracker.CALL = CALL ReferenceTracker.CONSTRUCT = CONSTRUCT ReferenceTracker.ESM = ESM /** * This is a predicate function for Array#filter. * @param {string} name A name part. * @param {number} index The index of the name. * @returns {boolean} `false` if it's default. */ function exceptDefault(name, index) { return !(index === 1 && name === "default") } eslint-utils-2.1.0/src/token-predicate.js000066400000000000000000000105171367166352100203670ustar00rootroot00000000000000/** * Negate the result of `this` calling. * @param {Token} token The token to check. * @returns {boolean} `true` if the result of `this(token)` is `false`. */ function negate0(token) { return !this(token) //eslint-disable-line no-invalid-this } /** * Creates the negate function of the given function. * @param {function(Token):boolean} f - The function to negate. * @returns {function(Token):boolean} Negated function. */ function negate(f) { return negate0.bind(f) } /** * Checks if the given token is an arrow token or not. * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is an arrow token. */ export function isArrowToken(token) { return token.value === "=>" && token.type === "Punctuator" } /** * Checks if the given token is a comma token or not. * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is a comma token. */ export function isCommaToken(token) { return token.value === "," && token.type === "Punctuator" } /** * Checks if the given token is a semicolon token or not. * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is a semicolon token. */ export function isSemicolonToken(token) { return token.value === ";" && token.type === "Punctuator" } /** * Checks if the given token is a colon token or not. * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is a colon token. */ export function isColonToken(token) { return token.value === ":" && token.type === "Punctuator" } /** * Checks if the given token is an opening parenthesis token or not. * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is an opening parenthesis token. */ export function isOpeningParenToken(token) { return token.value === "(" && token.type === "Punctuator" } /** * Checks if the given token is a closing parenthesis token or not. * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is a closing parenthesis token. */ export function isClosingParenToken(token) { return token.value === ")" && token.type === "Punctuator" } /** * Checks if the given token is an opening square bracket token or not. * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is an opening square bracket token. */ export function isOpeningBracketToken(token) { return token.value === "[" && token.type === "Punctuator" } /** * Checks if the given token is a closing square bracket token or not. * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is a closing square bracket token. */ export function isClosingBracketToken(token) { return token.value === "]" && token.type === "Punctuator" } /** * Checks if the given token is an opening brace token or not. * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is an opening brace token. */ export function isOpeningBraceToken(token) { return token.value === "{" && token.type === "Punctuator" } /** * Checks if the given token is a closing brace token or not. * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is a closing brace token. */ export function isClosingBraceToken(token) { return token.value === "}" && token.type === "Punctuator" } /** * Checks if the given token is a comment token or not. * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is a comment token. */ export function isCommentToken(token) { return ( token.type === "Line" || token.type === "Block" || token.type === "Shebang" ) } export const isNotArrowToken = negate(isArrowToken) export const isNotCommaToken = negate(isCommaToken) export const isNotSemicolonToken = negate(isSemicolonToken) export const isNotColonToken = negate(isColonToken) export const isNotOpeningParenToken = negate(isOpeningParenToken) export const isNotClosingParenToken = negate(isClosingParenToken) export const isNotOpeningBracketToken = negate(isOpeningBracketToken) export const isNotClosingBracketToken = negate(isClosingBracketToken) export const isNotOpeningBraceToken = negate(isOpeningBraceToken) export const isNotClosingBraceToken = negate(isClosingBraceToken) export const isNotCommentToken = negate(isCommentToken) eslint-utils-2.1.0/test/000077500000000000000000000000001367166352100151375ustar00rootroot00000000000000eslint-utils-2.1.0/test/find-variable.js000066400000000000000000000056071367166352100202100ustar00rootroot00000000000000import assert from "assert" import eslint from "eslint" import { findVariable } from "../src/" describe("The 'findVariable' function", () => { function getVariable(code, selector, withString = null) { const linter = new eslint.Linter() let variable = null linter.defineRule("test", context => ({ [selector](node) { variable = findVariable(context.getScope(), withString || node) }, })) linter.verify(code, { parserOptions: { ecmaVersion: 2018 }, rules: { test: "error" }, }) return variable } describe("should return the variable of a given Identifier node", () => { it("from the same scope.", () => { const variable = getVariable( "let a; foo(a)", "CallExpression Identifier[name='a']" ) assert.strictEqual(variable.name, "a") }) it("from nested blocks.", () => { const variable = getVariable( "let a; if (b) { foo(a) }", "CallExpression Identifier[name='a']" ) assert.strictEqual(variable.name, "a") }) it("from function blocks.", () => { const variable = getVariable( "let a; function f() { foo(a) }", "CallExpression Identifier[name='a']" ) assert.strictEqual(variable.name, "a") }) }) describe("should return the variable of a given Identifier node", () => { it("from the same scope.", () => { const variable = getVariable( "let a; foo(a)", "CallExpression Identifier[name='a']", "a" ) assert.strictEqual(variable.name, "a") }) it("from nested blocks.", () => { const variable = getVariable( "let a; if (b) { foo(a) }", "CallExpression Identifier[name='a']", "a" ) assert.strictEqual(variable.name, "a") }) it("from function blocks.", () => { const variable = getVariable( "let a; function f() { foo(a) }", "CallExpression Identifier[name='a']", "a" ) assert.strictEqual(variable.name, "a") }) }) it("should return global variables.", () => { const variable = getVariable( "let a; function f() { foo(a) }", "CallExpression Identifier[name='a']", "Object" ) assert.strictEqual(variable.name, "Object") }) it("should return null if it didn't exist.", () => { const variable = getVariable( "let a; function f() { foo(a) }", "CallExpression Identifier[name='a']", "x" ) assert.strictEqual(variable, null) }) }) eslint-utils-2.1.0/test/get-function-head-location.js000066400000000000000000000062161367166352100226110ustar00rootroot00000000000000import assert from "assert" import eslint from "eslint" import { getFunctionHeadLocation } from "../src/" describe("The 'getFunctionHeadLocation' function", () => { const expectedResults = { "function foo() {}": [0, 12], "(function foo() {})": [1, 13], "(function() {})": [1, 9], "function* foo() {}": [0, 13], "(function* foo() {})": [1, 14], "(function*() {})": [1, 10], "() => {}": [3, 5], "async () => {}": [9, 11], "({ foo: function foo() {} })": [3, 20], "({ foo: function() {} })": [3, 16], "({ ['foo']: function() {} })": [3, 20], "({ [foo]: function() {} })": [3, 18], "({ foo() {} })": [3, 6], "({ foo: function* foo() {} })": [3, 21], "({ foo: function*() {} })": [3, 17], "({ ['foo']: function*() {} })": [3, 21], "({ [foo]: function*() {} })": [3, 19], "({ *foo() {} })": [3, 7], "({ foo: async function foo() {} })": [3, 26], "({ foo: async function() {} })": [3, 22], "({ ['foo']: async function() {} })": [3, 26], "({ [foo]: async function() {} })": [3, 24], "({ async foo() {} })": [3, 12], "({ get foo() {} })": [3, 10], "({ set foo(a) {} })": [3, 10], "class A { constructor() {} }": [10, 21], "class A { foo() {} }": [10, 13], "class A { *foo() {} }": [10, 14], "class A { async foo() {} }": [10, 19], "class A { ['foo']() {} }": [10, 17], "class A { *['foo']() {} }": [10, 18], "class A { async ['foo']() {} }": [10, 23], "class A { [foo]() {} }": [10, 15], "class A { *[foo]() {} }": [10, 16], "class A { async [foo]() {} }": [10, 21], "class A { get foo() {} }": [10, 17], "class A { set foo(a) {} }": [10, 17], "class A { static foo() {} }": [10, 20], "class A { static *foo() {} }": [10, 21], "class A { static async foo() {} }": [10, 26], "class A { static get foo() {} }": [10, 24], "class A { static set foo(a) {} }": [10, 24], } for (const key of Object.keys(expectedResults)) { const expectedLoc = { start: { line: 1, column: expectedResults[key][0], }, end: { line: 1, column: expectedResults[key][1], }, } it(`should return "${JSON.stringify( expectedLoc )}" for "${key}".`, () => { const linter = new eslint.Linter() let actualLoc = null linter.defineRule("test", context => ({ ":function"(node) { actualLoc = getFunctionHeadLocation( node, context.getSourceCode() ) }, })) linter.verify( key, { rules: { test: "error" }, parserOptions: { ecmaVersion: 2018 }, }, "test.js", true ) assert.deepStrictEqual(actualLoc, expectedLoc) }) } }) eslint-utils-2.1.0/test/get-function-name-with-kind.js000066400000000000000000000067241367166352100227220ustar00rootroot00000000000000import assert from "assert" import eslint from "eslint" import { getFunctionNameWithKind } from "../src/" describe("The 'getFunctionNameWithKind' function", () => { const expectedResults = { "function foo() {}": "function 'foo'", "(function foo() {})": "function 'foo'", "(function() {})": "function", "function* foo() {}": "generator function 'foo'", "(function* foo() {})": "generator function 'foo'", "(function*() {})": "generator function", "() => {}": "arrow function", "async () => {}": "async arrow function", "const foo = () => {}": "arrow function 'foo'", "const foo = async () => {}": "async arrow function 'foo'", "foo = () => {}": "arrow function 'foo'", "foo = async () => {}": "async arrow function 'foo'", "foo.bar = () => {}": "arrow function", "foo.bar = async () => {}": "async arrow function", "({ foo: function foo() {} })": "method 'foo'", "({ foo: function() {} })": "method 'foo'", "({ ['foo']: function() {} })": "method 'foo'", "({ [foo]: function() {} })": "method", "({ foo() {} })": "method 'foo'", "({ foo: function* foo() {} })": "generator method 'foo'", "({ foo: function*() {} })": "generator method 'foo'", "({ ['foo']: function*() {} })": "generator method 'foo'", "({ [foo]: function*() {} })": "generator method", "({ *foo() {} })": "generator method 'foo'", "({ foo: async function foo() {} })": "async method 'foo'", "({ foo: async function() {} })": "async method 'foo'", "({ ['foo']: async function() {} })": "async method 'foo'", "({ [foo]: async function() {} })": "async method", "({ async foo() {} })": "async method 'foo'", "({ get foo() {} })": "getter 'foo'", "({ set foo(a) {} })": "setter 'foo'", "class A { constructor() {} }": "constructor", "class A { foo() {} }": "method 'foo'", "class A { *foo() {} }": "generator method 'foo'", "class A { async foo() {} }": "async method 'foo'", "class A { ['foo']() {} }": "method 'foo'", "class A { *['foo']() {} }": "generator method 'foo'", "class A { async ['foo']() {} }": "async method 'foo'", "class A { [foo]() {} }": "method", "class A { *[foo]() {} }": "generator method", "class A { async [foo]() {} }": "async method", "class A { get foo() {} }": "getter 'foo'", "class A { set foo(a) {} }": "setter 'foo'", "class A { static foo() {} }": "static method 'foo'", "class A { static *foo() {} }": "static generator method 'foo'", "class A { static async foo() {} }": "static async method 'foo'", "class A { static get foo() {} }": "static getter 'foo'", "class A { static set foo(a) {} }": "static setter 'foo'", } for (const key of Object.keys(expectedResults)) { it(`should return "${expectedResults[key]}" for "${key}".`, () => { const linter = new eslint.Linter() let actualResult = null linter.defineRule("test", () => ({ ":function"(node) { actualResult = getFunctionNameWithKind(node) }, })) linter.verify(key, { rules: { test: "error" }, parserOptions: { ecmaVersion: 2018 }, }) assert.strictEqual(actualResult, expectedResults[key]) }) } }) eslint-utils-2.1.0/test/get-innermost-scope.js000066400000000000000000000052611367166352100214030ustar00rootroot00000000000000import assert from "assert" import eslint from "eslint" import { getInnermostScope } from "../src/" describe("The 'getInnermostScope' function", () => { let i = 0 for (const { code, parserOptions, selectNode, selectScope } of [ { code: "let a = 0", parserOptions: {}, selectNode: node => node, selectScope: scope => scope, }, { code: "let a = 0", parserOptions: { ecmaFeatures: { globalReturn: true } }, selectNode: node => node, selectScope: scope => scope.childScopes[0], }, { code: "let a = 0", parserOptions: { sourceType: "module" }, selectNode: node => node, selectScope: scope => scope.childScopes[0], }, { code: "a; { b; { c; } d; } e;", parserOptions: {}, selectNode: node => node.body[0], selectScope: scope => scope, }, { code: "a; { b; { c; } d; } e;", parserOptions: {}, selectNode: node => node.body[2], selectScope: scope => scope, }, { code: "a; { b; { c; } d; } e;", parserOptions: {}, selectNode: node => node.body[1].body[0], selectScope: scope => scope.childScopes[0], }, { code: "a; { b; { c; } d; } e;", parserOptions: {}, selectNode: node => node.body[1].body[2], selectScope: scope => scope.childScopes[0], }, { code: "a; { b; { c; } d; } e;", parserOptions: {}, selectNode: node => node.body[1].body[1].body[0], selectScope: scope => scope.childScopes[0].childScopes[0], }, ]) { it(`should return the innermost scope (${++i})`, () => { const linter = new eslint.Linter() let actualScope = null let expectedScope = null linter.defineRule("test", context => ({ Program(node) { const scope = context.getScope() actualScope = getInnermostScope(scope, selectNode(node)) expectedScope = selectScope(scope) }, })) linter.verify(code, { parserOptions: Object.assign( { ecmaVersion: 2018 }, parserOptions ), rules: { test: "error" }, }) assert.notStrictEqual(expectedScope, null) // assert.strictEqual makes tooooo large diff. assert(actualScope === expectedScope) }) } }) eslint-utils-2.1.0/test/get-property-name.js000066400000000000000000000045361367166352100210640ustar00rootroot00000000000000import assert from "assert" import eslint from "eslint" import { getPropertyName } from "../src/" describe("The 'getPropertyName' function", () => { for (const { code, expected } of [ { code: "a.b", expected: "b" }, { code: "a['b']", expected: "b" }, { code: "a[`b`]", expected: "b" }, { code: "a[100]", expected: "100" }, { code: "a[b]", expected: null }, { code: "a['a' + 'b']", expected: "ab" }, { code: "a[tag`b`]", expected: null }, { code: "a[`${b}`]", expected: null }, //eslint-disable-line no-template-curly-in-string { code: "({b: 1})", expected: "b" }, { code: "({0x10: 1})", expected: "16" }, { code: "({'foo': 1})", expected: "foo" }, { code: "({b() {}})", expected: "b" }, { code: "({get b() {}})", expected: "b" }, { code: "({['b']: 1})", expected: "b" }, { code: "({['b']() {}})", expected: "b" }, { code: "({[`b`]: 1})", expected: "b" }, { code: "({[100]: 1})", expected: "100" }, { code: "({[b]: 1})", expected: null }, { code: "({['a' + 'b']: 1})", expected: "ab" }, { code: "({[tag`b`]: 1})", expected: null }, { code: "({[`${b}`]: 1})", expected: null }, //eslint-disable-line no-template-curly-in-string { code: "(class {b() {}})", expected: "b" }, { code: "(class {get b() {}})", expected: "b" }, { code: "(class {['b']() {}})", expected: "b" }, { code: "(class {[100]() {}})", expected: "100" }, { code: "(class {[b]() {}})", expected: null }, { code: "(class {['a' + 'b']() {}})", expected: "ab" }, { code: "(class {[tag`b`]() {}})", expected: null }, { code: "(class {[`${b}`]() {}})", expected: null }, //eslint-disable-line no-template-curly-in-string ]) { it(`should return ${JSON.stringify(expected)} from ${code}`, () => { const linter = new eslint.Linter() let actual = null linter.defineRule("test", () => ({ "Property,MethodDefinition,MemberExpression"(node) { actual = getPropertyName(node) }, })) linter.verify(code, { parserOptions: { ecmaVersion: 2018 }, rules: { test: "error" }, }) assert.strictEqual(actual, expected) }) } }) eslint-utils-2.1.0/test/get-static-value.js000066400000000000000000000262471367166352100206660ustar00rootroot00000000000000import assert from "assert" import eslint from "eslint" import semver from "semver" import { getStaticValue } from "../src/" describe("The 'getStaticValue' function", () => { for (const { code, expected, noScope = false } of [ { code: "[]", expected: { value: [] } }, { code: "[1, 2, 3]", expected: { value: [1, 2, 3] } }, { code: "[,, 3]", expected: { value: [, , 3] } }, //eslint-disable-line no-sparse-arrays { code: "[1, ...[2, 3]]", expected: { value: [1, 2, 3] } }, { code: "[0, a]", expected: null }, { code: "[0, ...a]", expected: null }, { code: "a = 1 + 2", expected: { value: 3 } }, { code: "a += 1 + 2", expected: null }, { code: "a in obj", expected: null }, { code: "obj instanceof Object", expected: null }, { code: "1 == '1'", expected: { value: true } }, { code: "1 != '1'", expected: { value: false } }, { code: "1 === '1'", expected: { value: false } }, { code: "1 !== '1'", expected: { value: true } }, { code: "1 < '1'", expected: { value: false } }, { code: "1 <= '1'", expected: { value: true } }, { code: "1 > '1'", expected: { value: false } }, { code: "1 >= '1'", expected: { value: true } }, { code: "1 << '1'", expected: { value: 2 } }, { code: "1 >> '1'", expected: { value: 0 } }, { code: "1 >>> '1'", expected: { value: 0 } }, { code: "1 + '1'", expected: { value: "11" } }, { code: "1 + 2", expected: { value: 3 } }, { code: "1 - 2", expected: { value: -1 } }, { code: "1 * 2", expected: { value: 2 } }, { code: "1 / 2", expected: { value: 0.5 } }, { code: "1 % 2", expected: { value: 1 } }, { code: "2 ** 2", expected: { value: 4 } }, { code: "1 | 2", expected: { value: 3 } }, { code: "1 ^ 15", expected: { value: 14 } }, { code: "3 & 2", expected: { value: 2 } }, { code: "a + 1", expected: null }, { code: "String(7)", expected: { value: "7" } }, { code: "Math.round(0.7)", expected: { value: 1 } }, { code: "Math['round'](0.4)", expected: { value: 0 } }, { code: "foo(7)", expected: null }, { code: "obj.foo(7)", expected: null }, { code: "Math.round(a)", expected: null }, { code: "true ? 1 : c", expected: { value: 1 } }, { code: "false ? b : 2", expected: { value: 2 } }, { code: "a ? 1 : 2", expected: null }, { code: "true ? b : 2", expected: null }, { code: "false ? 1 : c", expected: null }, { code: "undefined", expected: { value: undefined } }, { code: "var undefined; undefined", expected: null }, { code: "const undefined = 1; undefined", expected: { value: 1 } }, { code: "const a = 2; a", expected: { value: 2 } }, { code: "let a = 2; a", expected: null }, { code: "const a = 2; a", expected: null, noScope: true }, { code: "const a = { b: 7 }; a.b", expected: { value: 7 } }, { code: "null", expected: { value: null } }, { code: "true", expected: { value: true } }, { code: "false", expected: { value: false } }, { code: "1", expected: { value: 1 } }, { code: "'hello'", expected: { value: "hello" } }, { code: "/foo/gu", expected: { value: /foo/gu } }, { code: "true && 1", expected: { value: 1 } }, { code: "false && a", expected: { value: false } }, { code: "true || a", expected: { value: true } }, { code: "false || 2", expected: { value: 2 } }, { code: "true && a", expected: null }, { code: "false || a", expected: null }, { code: "a && 1", expected: null }, { code: "Symbol.iterator", expected: { value: Symbol.iterator } }, { code: "Symbol['iter' + 'ator']", expected: { value: Symbol.iterator }, }, { code: "Symbol[iterator]", expected: null }, { code: "Object.freeze", expected: { value: Object.freeze } }, { code: "Object.xxx", expected: { value: undefined } }, { code: "new Array(2)", expected: null }, { code: "new Array(len)", expected: null }, { code: "({})", expected: { value: {} } }, { code: "({a: 1, b: 2, c: 3})", expected: { value: { a: 1, b: 2, c: 3 } }, }, { code: "const obj = {b: 2}; ({a: 1, ...obj})", expected: { value: { a: 1, b: 2 } }, }, { code: "var obj = {b: 2}; ({a: 1, ...obj})", expected: null }, { code: "({ get a() {} })", expected: null }, { code: "({ a })", expected: null }, { code: "({ a: b })", expected: null }, { code: "({ [a]: 1 })", expected: null }, { code: "(a, b, 3)", expected: { value: 3 } }, { code: "(1, b)", expected: null }, { code: "`hello`", expected: { value: "hello" } }, { code: "const ll = 'll'; `he${ll}o`", expected: { value: "hello" } }, //eslint-disable-line no-template-curly-in-string { code: "String.raw`\\unicode`", expected: { value: "\\unicode" } }, { code: "`he${a}o`", expected: null }, //eslint-disable-line no-template-curly-in-string { code: "x`hello`", expected: null }, { code: "-1", expected: { value: -1 } }, { code: "+'1'", expected: { value: 1 } }, { code: "!0", expected: { value: true } }, { code: "~-1", expected: { value: 0 } }, { code: "typeof 0", expected: { value: "number" } }, { code: "void a.b", expected: { value: undefined } }, { code: "+a", expected: null }, { code: "delete a.b", expected: null }, { code: "!function(){ return true }", expected: null }, { code: "'' + Symbol()", expected: null }, { code: `const eventName = "click" const aMap = Object.freeze({ click: 777 }) ;\`on\${eventName} : \${aMap[eventName]}\``, expected: { value: "onclick : 777" }, }, { code: 'Function("return process.env.npm_name")()', expected: null, }, { code: 'new Function("return process.env.npm_name")()', expected: null, }, { code: '({}.constructor.constructor("return process.env.npm_name")())', expected: null, }, { code: 'JSON.stringify({a:1}, new {}.constructor.constructor("console.log(\\"code injected\\"); process.exit(1)"), 2)', expected: null, }, { code: 'Object.create(null, {a:{get:new {}.constructor.constructor("console.log(\\"code injected\\"); process.exit(1)")}}).a', expected: null, }, { code: "RegExp.$1", expected: null, }, ...(semver.gte(eslint.CLIEngine.version, "6.0.0") ? [ { code: "const a = null, b = 42; a ?? b", expected: { value: 42 }, }, { code: "const a = undefined, b = 42; a ?? b", expected: { value: 42 }, }, { code: "const a = false, b = 42; a ?? b", expected: { value: false }, }, { code: "const a = 42, b = null; a ?? b", expected: { value: 42 }, }, { code: "const a = 42, b = undefined; a ?? b", expected: { value: 42 }, }, { code: "const a = { b: { c: 42 } }; a?.b?.c", expected: { value: 42 }, }, { code: "const a = { b: { c: 42 } }; a?.b?.['c']", expected: { value: 42 }, }, { code: "const a = { b: null }; a?.b?.c", expected: { value: undefined }, }, { code: "const a = { b: undefined }; a?.b?.c", expected: { value: undefined }, }, { code: "const a = { b: null }; a?.b?.['c']", expected: { value: undefined }, }, { code: "const a = null; a?.b?.c", expected: { value: undefined }, }, { code: "const a = null; a?.b.c", expected: { value: undefined }, }, { code: "const a = void 0; a?.b.c", expected: { value: undefined }, }, { code: "const a = { b: { c: 42 } }; (a?.b).c", expected: { value: 42 }, }, { code: "const a = null; (a?.b).c", expected: null, }, { code: "const a = { b: null }; (a?.b).c", expected: null, }, { code: "const a = { b: { c: String } }; a?.b?.c?.(42)", expected: { value: "42" }, }, { code: "const a = null; a?.b?.c?.(42)", expected: { value: undefined }, }, { code: "const a = { b: { c: String } }; a?.b.c(42)", expected: { value: "42" }, }, { code: "const a = null; a?.b.c(42)", expected: { value: undefined }, }, { code: "null?.()", expected: { value: undefined }, }, { code: "const a = null; a?.()", expected: { value: undefined }, }, { code: "a?.()", expected: null, }, ] : []), ]) { it(`should return ${JSON.stringify(expected)} from ${code}`, () => { const linter = new eslint.Linter() let actual = null linter.defineRule("test", context => ({ ExpressionStatement(node) { actual = getStaticValue( node, noScope ? null : context.getScope() ) }, })) linter.verify(code, { env: { es6: true }, parserOptions: { ecmaVersion: semver.gte(eslint.CLIEngine.version, "6.0.0") ? 2020 : 2018, }, rules: { test: "error" }, }) if (actual == null) { assert.strictEqual(actual, expected) } else { assert.deepStrictEqual(actual, expected) } }) } }) eslint-utils-2.1.0/test/get-string-if-constant.js000066400000000000000000000051131367166352100220030ustar00rootroot00000000000000import assert from "assert" import eslint from "eslint" import { getStringIfConstant } from "../src/" describe("The 'getStringIfConstant' function", () => { for (const { code, expected } of [ { code: "true", expected: "true" }, { code: "false", expected: "false" }, { code: "0x100", expected: "256" }, { code: "3.14e+2", expected: "314" }, { code: '"test"', expected: "test" }, { code: "'abc'", expected: "abc" }, { code: "`abc`", expected: "abc" }, { code: "null", expected: "null" }, { code: "/a/", expected: "/a/" }, { code: "/a/g", expected: "/a/g" }, { code: "id", expected: null }, { code: "tag`foo`", expected: null }, { code: "`aaa${id}bbb`", expected: null }, //eslint-disable-line no-template-curly-in-string { code: "1 + 2", expected: "3" }, { code: "'a' + 'b'", expected: "ab" }, { code: "/(?\\w+)\\k/gu", expected: "/(?\\w+)\\k/gu" }, ]) { it(`should return ${JSON.stringify(expected)} from ${code}`, () => { const linter = new eslint.Linter() let actual = null linter.defineRule("test", () => ({ "Program > ExpressionStatement > *"(node) { actual = getStringIfConstant(node) }, })) linter.verify(code, { parserOptions: { ecmaVersion: 2018 }, rules: { test: "error" }, }) assert.strictEqual(actual, expected) }) } describe("with the 2nd argument 'initialScope',", () => { for (const { code, expected } of [ { code: "id", expected: null }, { code: "const id = 'abc'; id", expected: "abc" }, { code: "let id = 'abc'; id", expected: null }, { code: "var id = 'abc'; id", expected: null }, { code: "const id = otherId; id", expected: null }, ]) { it(`should return ${JSON.stringify(expected)} from ${code}`, () => { const linter = new eslint.Linter() let actual = null linter.defineRule("test", context => ({ "Program > ExpressionStatement > *"(node) { actual = getStringIfConstant(node, context.getScope()) }, })) linter.verify(code, { parserOptions: { ecmaVersion: 2018 }, rules: { test: "error" }, }) assert.strictEqual(actual, expected) }) } }) }) eslint-utils-2.1.0/test/has-side-effect.js000066400000000000000000000206071367166352100204310ustar00rootroot00000000000000import assert from "assert" import eslint from "eslint" import semver from "semver" import dp from "dot-prop" import { hasSideEffect } from "../src/" describe("The 'hasSideEffect' function", () => { for (const { code, key = "body.0.expression", options, expected } of [ { code: "777", options: undefined, expected: false, }, { code: "foo", options: undefined, expected: false, }, { code: "a = 0", options: undefined, expected: true, }, { code: "async function f() { await g }", key: "body.0.body.body.0.expression", options: undefined, expected: true, }, { code: "a + b", options: undefined, expected: false, }, { code: "a + b", options: { considerImplicitTypeConversion: true }, expected: true, }, { code: "1 + 2", options: { considerImplicitTypeConversion: true }, expected: false, }, { code: "f()", options: undefined, expected: true, }, ...(semver.gte(eslint.CLIEngine.version, "6.0.0") ? [ { code: "f?.()", options: undefined, expected: true, }, ] : []), { code: "a + f()", options: undefined, expected: true, }, ...(semver.gte(eslint.CLIEngine.version, "6.0.0") ? [ { code: "a + f?.()", options: undefined, expected: true, }, ] : []), { code: "obj.a", options: undefined, expected: false, }, { code: "obj.a", options: { considerGetters: true }, expected: true, }, ...(semver.gte(eslint.CLIEngine.version, "6.0.0") ? [ { code: "obj?.a", options: undefined, expected: false, }, { code: "obj?.a", options: { considerGetters: true }, expected: true, }, ] : []), { code: "obj[a]", options: undefined, expected: false, }, { code: "obj[a]", options: { considerGetters: true }, expected: true, }, { code: "obj[a]", options: { considerImplicitTypeConversion: true }, expected: true, }, ...(semver.gte(eslint.CLIEngine.version, "6.0.0") ? [ { code: "obj?.[a]", options: undefined, expected: false, }, { code: "obj?.[a]", options: { considerGetters: true }, expected: true, }, { code: "obj?.[a]", options: { considerImplicitTypeConversion: true }, expected: true, }, ] : []), { code: "obj[0]", options: { considerImplicitTypeConversion: true }, expected: false, }, { code: "obj['@@abc']", options: { considerImplicitTypeConversion: true }, expected: false, }, { code: "(class { f() { a++ } })", options: undefined, expected: false, }, { code: "(class { f() { a++ } })", options: { considerImplicitTypeConversion: true }, expected: false, }, { code: "(class { [f]() { a++ } })", options: undefined, expected: false, }, { code: "(class { [f]() { a++ } })", options: { considerImplicitTypeConversion: true }, expected: true, }, { code: "(class { [0]() { a++ } })", options: { considerImplicitTypeConversion: true }, expected: false, }, { code: "(class { ['@@']() { a++ } })", options: { considerImplicitTypeConversion: true }, expected: false, }, { code: "new C()", options: undefined, expected: true, }, { code: "({ key: 1 })", options: undefined, expected: false, }, { code: "({ key: 1 })", options: { considerImplicitTypeConversion: true }, expected: false, }, { code: "({ [key]: 1 })", options: undefined, expected: false, }, { code: "({ [key]: 1 })", options: { considerImplicitTypeConversion: true }, expected: true, }, { code: "({ [0]: 1 })", options: { considerImplicitTypeConversion: true }, expected: false, }, { code: "({ ['@@']: 1 })", options: { considerImplicitTypeConversion: true }, expected: false, }, { code: "+1", options: undefined, expected: false, }, { code: "+1", options: { considerImplicitTypeConversion: true }, expected: false, }, { code: "-1", options: undefined, expected: false, }, { code: "-1", options: { considerImplicitTypeConversion: true }, expected: false, }, { code: "+a", options: undefined, expected: false, }, { code: "+a", options: { considerImplicitTypeConversion: true }, expected: true, }, { code: "delete obj.a", options: undefined, expected: true, }, { code: "++a", options: undefined, expected: true, }, { code: "function* g() { yield 1 }", key: "body.0.body.body.0.expression", options: undefined, expected: true, }, { code: "(a, b, c)", options: undefined, expected: false, }, { code: "(a, b, ++c)", options: undefined, expected: true, }, // Skip the definition body. { code: "(function f(a) { a++ })", options: undefined, expected: false, }, { code: "((a) => { a++ })", options: undefined, expected: false, }, { code: "((a) => a++)", options: undefined, expected: false, }, ]) { it(`should return ${expected} on the code \`${code}\` and the options \`${JSON.stringify( options )}\``, () => { const linter = new eslint.Linter() let actual = null linter.defineRule("test", context => ({ Program(node) { actual = hasSideEffect( dp.get(node, key), context.getSourceCode(), options ) }, })) const messages = linter.verify(code, { env: { es6: true }, parserOptions: { ecmaVersion: semver.gte(eslint.CLIEngine.version, "6.0.0") ? 2020 : 2018, }, rules: { test: "error" }, }) assert.strictEqual( messages.length, 0, messages[0] && messages[0].message ) assert.strictEqual(actual, expected) }) } }) eslint-utils-2.1.0/test/is-parenthesized.js000066400000000000000000000216731367166352100207640ustar00rootroot00000000000000import assert from "assert" import dotProp from "dot-prop" import eslint from "eslint" import { isParenthesized } from "../src/" describe("The 'isParenthesized' function", () => { for (const { code, expected } of [ { code: "777", expected: { "body.0": false, "body.0.expression": false, }, }, { code: "(777)", expected: { "body.0": false, "body.0.expression": true, }, }, { code: "(777 + 223)", expected: { "body.0": false, "body.0.expression": true, "body.0.expression.left": false, "body.0.expression.right": false, }, }, { code: "(777) + 223", expected: { "body.0": false, "body.0.expression": false, "body.0.expression.left": true, "body.0.expression.right": false, }, }, { code: "((777) + 223)", expected: { "body.0": false, "body.0.expression": true, "body.0.expression.left": true, "body.0.expression.right": false, }, }, { code: "f()", expected: { "body.0": false, "body.0.expression": false, "body.0.expression.arguments.0": false, }, }, { code: "(f())", expected: { "body.0": false, "body.0.expression": true, "body.0.expression.arguments.0": false, }, }, { code: "f(a)", expected: { "body.0": false, "body.0.expression": false, "body.0.expression.arguments.0": false, }, }, { code: "f((a))", expected: { "body.0": false, "body.0.expression": false, "body.0.expression.arguments.0": true, }, }, { code: "f(a,b)", expected: { "body.0": false, "body.0.expression": false, "body.0.expression.arguments.0": false, "body.0.expression.arguments.1": false, }, }, { code: "f((a),b)", expected: { "body.0": false, "body.0.expression": false, "body.0.expression.arguments.0": true, "body.0.expression.arguments.1": false, }, }, { code: "f(a,(b))", expected: { "body.0": false, "body.0.expression": false, "body.0.expression.arguments.0": false, "body.0.expression.arguments.1": true, }, }, { code: "new f(a)", expected: { "body.0": false, "body.0.expression": false, "body.0.expression.arguments.0": false, }, }, { code: "new f((a))", expected: { "body.0": false, "body.0.expression": false, "body.0.expression.arguments.0": true, }, }, { code: "do f(); while (a)", expected: { "body.0": false, "body.0.test": false, "body.0.body": false, "body.0.body.expression": false, }, }, { code: "do (f()); while ((a))", expected: { "body.0": false, "body.0.test": true, "body.0.body": false, "body.0.body.expression": true, }, }, { code: "if (a) b()", expected: { "body.0": false, "body.0.test": false, "body.0.consequent": false, "body.0.consequent.expression": false, }, }, { code: "if ((a)) (b())", expected: { "body.0": false, "body.0.test": true, "body.0.consequent": false, "body.0.consequent.expression": true, }, }, { code: "while (a) b()", expected: { "body.0": false, "body.0.test": false, "body.0.body": false, "body.0.body.expression": false, }, }, { code: "while ((a)) (b())", expected: { "body.0": false, "body.0.test": true, "body.0.body": false, "body.0.body.expression": true, }, }, { code: "switch (a) {}", expected: { "body.0": false, "body.0.discriminant": false, }, }, { code: "switch ((a)) {}", expected: { "body.0": false, "body.0.discriminant": true, }, }, { code: "with (a) {}", expected: { "body.0": false, "body.0.object": false, }, }, { code: "with ((a)) {}", expected: { "body.0": false, "body.0.object": true, }, }, ]) { describe(`on the code \`${code}\``, () => { for (const key of Object.keys(expected)) { it(`should return ${expected[key]} at "${key}"`, () => { const linter = new eslint.Linter() let actual = null linter.defineRule("test", context => ({ Program(node) { actual = isParenthesized( dotProp.get(node, key), context.getSourceCode() ) }, })) const messages = linter.verify(code, { env: { es6: true }, parserOptions: { ecmaVersion: 2018 }, rules: { test: "error" }, }) assert.strictEqual( messages.length, 0, messages[0] && messages[0].message ) assert.strictEqual(actual, expected[key]) }) } }) } for (const { code, expected } of [ { code: "777", expected: { "body.0": false, "body.0.expression": false, }, }, { code: "(777)", expected: { "body.0": false, "body.0.expression": false, }, }, { code: "((777))", expected: { "body.0": false, "body.0.expression": true, }, }, { code: "if (a) ;", expected: { "body.0": false, "body.0.test": false, }, }, { code: "if ((a)) ;", expected: { "body.0": false, "body.0.test": false, }, }, { code: "if (((a))) ;", expected: { "body.0": false, "body.0.test": true, }, }, ]) { describe(`on the code \`${code}\` and 2 times`, () => { for (const key of Object.keys(expected)) { it(`should return ${expected[key]} at "${key}"`, () => { const linter = new eslint.Linter() let actual = null linter.defineRule("test", context => ({ Program(node) { actual = isParenthesized( 2, dotProp.get(node, key), context.getSourceCode() ) }, })) const messages = linter.verify(code, { env: { es6: true }, parserOptions: { ecmaVersion: 2018 }, rules: { test: "error" }, }) assert.strictEqual( messages.length, 0, messages[0] && messages[0].message ) assert.strictEqual(actual, expected[key]) }) } }) } }) eslint-utils-2.1.0/test/pattern-matcher.js000066400000000000000000000265041367166352100206020ustar00rootroot00000000000000import assert from "assert" import { PatternMatcher } from "../src/" const NAMED_CAPTURE_GROUP_SUPPORTED = (() => { try { new RegExp("(?)", "u") //eslint-disable-line no-new, prefer-regex-literals, @mysticatea/node/no-unsupported-features/es-syntax return true } catch (_error) { return false } })() /** * Create a new RegExpExecArray. * @param {string[]} subStrings The substrings. * @param {number} index The index. * @param {string} input The input. * @returns {RegExpExecArray} The created object. */ function newRegExpExecArray(subStrings, index, input) { Object.assign(subStrings, { index, input }) if (NAMED_CAPTURE_GROUP_SUPPORTED) { subStrings.groups = undefined } return subStrings } describe("The 'PatternMatcher' class:", () => { describe("the constructor", () => { it("should throw TypeError if non-RegExp value was given.", () => { for (const value of [ undefined, null, 1, "foo", () => { // empty }, { exec() { // empty }, }, ]) { assert.throws( () => new PatternMatcher(value), /^TypeError: 'pattern' should be a RegExp instance\.$/u ) } }) it("should throw Error if the RegExp value does not have 'g' flag.", () => { for (const value of [/foo/u, /bar/imu]) { assert.throws( () => new PatternMatcher(value), /^Error: 'pattern' should contains 'g' flag\.$/u ) } }) }) describe("the 'execAll' method", () => { describe("with no options", () => { for (const { str, expected } of [ { str: "", expected: [] }, { str: "abc", expected: [] }, { str: String.raw`\foo`, expected: [] }, { str: String.raw`\\\foo`, expected: [] }, { str: String.raw`\a\foo`, expected: [] }, { str: "foo", expected: [newRegExpExecArray(["foo"], 0, "foo")], }, { str: String.raw`\\foo`, expected: [ newRegExpExecArray(["foo"], 2, String.raw`\\foo`), ], }, { str: String.raw`\\\\foo`, expected: [ newRegExpExecArray(["foo"], 4, String.raw`\\\\foo`), ], }, { str: "-foofoofooabcfoo-", expected: [ newRegExpExecArray(["foo"], 1, "-foofoofooabcfoo-"), newRegExpExecArray(["foo"], 4, "-foofoofooabcfoo-"), newRegExpExecArray(["foo"], 7, "-foofoofooabcfoo-"), newRegExpExecArray(["foo"], 13, "-foofoofooabcfoo-"), ], }, { str: String.raw`-foo\foofooabcfoo-`, expected: [ newRegExpExecArray( ["foo"], 1, String.raw`-foo\foofooabcfoo-` ), newRegExpExecArray( ["foo"], 8, String.raw`-foo\foofooabcfoo-` ), newRegExpExecArray( ["foo"], 14, String.raw`-foo\foofooabcfoo-` ), ], }, ]) { it(`should return ${JSON.stringify( expected )} in ${JSON.stringify(str)}.`, () => { const matcher = new PatternMatcher(/foo/gu) const actual = Array.from(matcher.execAll(str)) assert.deepStrictEqual(actual, expected) }) } for (const { str, expected } of [ { str: "ab0c", expected: [newRegExpExecArray(["b0", "b", "0"], 1, "ab0c")], }, { str: "a1b2c3", expected: [ newRegExpExecArray(["a1", "a", "1"], 0, "a1b2c3"), newRegExpExecArray(["b2", "b", "2"], 2, "a1b2c3"), newRegExpExecArray(["c3", "c", "3"], 4, "a1b2c3"), ], }, ]) { it(`should return ${JSON.stringify( expected )} in ${JSON.stringify(str)}.`, () => { const matcher = new PatternMatcher(/(\w)(\d)/gu) const actual = Array.from(matcher.execAll(str)) assert.deepStrictEqual(actual, expected) }) } it("should iterate for two strings in parallel.", () => { const matcher = new PatternMatcher(/\w/gu) const expected1 = [ newRegExpExecArray(["a"], 0, "a--b-c"), newRegExpExecArray(["b"], 3, "a--b-c"), newRegExpExecArray(["c"], 5, "a--b-c"), ] const expected2 = [ newRegExpExecArray(["a"], 1, "-ab-c-"), newRegExpExecArray(["b"], 2, "-ab-c-"), newRegExpExecArray(["c"], 4, "-ab-c-"), ] const actual1 = [] const actual2 = [] const it1 = matcher.execAll("a--b-c") const it2 = matcher.execAll("-ab-c-") { let ret1 = null let ret2 = null while ( ((ret1 = it1.next()), (ret2 = it2.next()), !ret1.done && !ret2.done) ) { actual1.push(ret1.value) actual2.push(ret2.value) } } assert.deepStrictEqual(actual1, expected1) assert.deepStrictEqual(actual2, expected2) }) }) describe("with 'escaped:true' option", () => { for (const { str, expected } of [ { str: "foo", expected: [newRegExpExecArray(["foo"], 0, "foo")], }, { str: String.raw`\foo`, expected: [ newRegExpExecArray(["foo"], 1, String.raw`\foo`), ], }, { str: String.raw`\\foo`, expected: [ newRegExpExecArray(["foo"], 2, String.raw`\\foo`), ], }, { str: String.raw`\\\foo`, expected: [ newRegExpExecArray(["foo"], 3, String.raw`\\\foo`), ], }, { str: String.raw`\\\\foo`, expected: [ newRegExpExecArray(["foo"], 4, String.raw`\\\\foo`), ], }, ]) { it(`should return ${JSON.stringify( expected )} in ${JSON.stringify(str)}.`, () => { const matcher = new PatternMatcher(/foo/gu, { escaped: true, }) const actual = Array.from(matcher.execAll(str)) assert.deepStrictEqual(actual, expected) }) } }) }) describe("the 'test' method", () => { for (const { str, expected } of [ { str: "", expected: false }, { str: "abc", expected: false }, { str: String.raw`\foo`, expected: false }, { str: String.raw`\\\foo`, expected: false }, { str: String.raw`\a\foo`, expected: false }, { str: String.raw`-\foo\foo\fooabc\foo-`, expected: false }, { str: "foo", expected: true }, { str: String.raw`\\foo`, expected: true }, { str: String.raw`\\\\foo`, expected: true }, { str: "-foofoofooabcfoo-", expected: true }, { str: String.raw`-foo\foofooabcfoo-`, expected: true }, ]) { it(`should return ${expected} in ${JSON.stringify(str)}.`, () => { const matcher = new PatternMatcher(/foo/gu) const actual = matcher.test(str) assert.deepStrictEqual(actual, expected) }) } }) describe("the 'Symbol.replace' method", () => { for (const { pattern, str, replacer, expected } of [ { str: "", replacer: "xyz", expected: "" }, { str: "123", replacer: "xyz", expected: "123" }, { str: String.raw`1\a2\b3`, replacer: "x", expected: String.raw`1\a2\b3`, }, { str: String.raw`1a2\b3`, replacer: "x", expected: String.raw`1x2\b3`, }, { str: String.raw`1a2b3`, replacer: "x", expected: String.raw`1x2x3`, }, { str: "abc", replacer: "x", expected: "xxx" }, { str: "abc", replacer: "$$x", expected: "$x$x$x" }, { str: "abc", replacer: "$$&", expected: "$&$&$&" }, { str: "abc", replacer: "$$$&", expected: "$a$b$c" }, { str: "abc", replacer: "$&", expected: "abc" }, { str: "abc", replacer: "$'$`", expected: "bccaab" }, { str: String.raw`a\bc`, replacer: "$'$`", expected: String.raw`\bc\ba\b`, }, { str: "abc", replacer: "$0", expected: "$0$0$0" }, { str: "abc", replacer: "$1", expected: "$1$1$1" }, { pattern: /a(b)/gu, str: "abc", replacer: "$1", expected: "bc", }, ]) { it(`should return ${expected} in ${JSON.stringify( str )} and ${JSON.stringify(replacer)}.`, () => { const matcher = new PatternMatcher(pattern || /[a-c]/gu) const actual = str.replace(matcher, replacer) assert.deepStrictEqual(actual, expected) }) } it("should pass the correct arguments to replacers.", () => { const matcher = new PatternMatcher(/(\w)(\d)/gu) const actualArgs = [] const actual = "abc1d2efg".replace(matcher, (...args) => { actualArgs.push(args) return "x" }) assert.deepStrictEqual(actualArgs, [ ["c1", "c", "1", 2, "abc1d2efg"], ["d2", "d", "2", 4, "abc1d2efg"], ]) assert.deepStrictEqual(actual, "abxxefg") }) }) }) eslint-utils-2.1.0/test/reference-tracker.js000066400000000000000000001061541367166352100210730ustar00rootroot00000000000000import assert from "assert" import eslint from "eslint" import semver from "semver" import { CALL, CONSTRUCT, ESM, READ, ReferenceTracker } from "../src/" const config = { parserOptions: { ecmaVersion: semver.gte(eslint.CLIEngine.version, "6.0.0") ? 2020 : 2018, sourceType: "module", }, globals: { Reflect: false }, rules: { test: "error" }, } describe("The 'ReferenceTracker' class:", () => { describe("the 'iterateGlobalReferences' method", () => { for (const { description, code, traceMap, expected } of [ { description: "should iterate the references of a given global variable.", code: "var x = Object; { let Object; var y = Object }", traceMap: { Object: { [READ]: 1, foo: { [CALL]: 2 }, Foo: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "Identifier" }, path: ["Object"], type: READ, info: 1, }, ], }, { description: "should iterate the member references of a given global variable, with MemberExpression", code: [ "Object.a; Object.a(); new Object.a();", "Object.b; Object.b(); new Object.b();", "Object.c; Object.c(); new Object.c();", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["Object", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["Object", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should iterate the member references of a given global variable, with VariableDeclarator", code: [ "var x = Object;", "x.a; x.a(); new x.a();", "x.b; x.b(); new x.b();", "x.c; x.c(); new x.c();", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["Object", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["Object", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should iterate the member references of a given global variable, with VariableDeclarator 2", code: [ "var x = Object, a = x.a, b = x.b, c = x.c;", "a; a(); new a();", "b; b(); new b();", "c; c(); new c();", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["Object", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["Object", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should iterate the member references of a given global variable, with AssignmentExpression", code: [ "var x, a, b, c;", "a = (x = Object).a; b = x.b; c = x.c;", "a; a(); new a();", "b; b(); new b();", "c; c(); new c();", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "CallExpression" }, path: ["Object", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["Object", "c"], type: CONSTRUCT, info: 3, }, { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, ], }, { description: "should iterate the member references of a given global variable, with destructuring", code: [ "var {a, b, c} = Object;", "a; a(); new a();", "b; b(); new b();", "c; c(); new c();", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "Property" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["Object", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["Object", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should iterate the member references of a given global variable, with AssignmentPattern", code: [ "var {x: {a, b, c} = Object} = {};", "a; a(); new a();", "b; b(); new b();", "c; c(); new c();", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "Property" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["Object", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["Object", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should iterate the member references of a given global variable, with 'window'.", code: [ "/*global window */", "var {Object: {a, b, c}} = window;", "a; a(); new a();", "b; b(); new b();", "c; c(); new c();", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "Property" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["Object", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["Object", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should iterate the member references of a given global variable, with 'global'.", code: [ "/*global global */", "global.Object.a;", "global.Object.b; global.Object.b(); new global.Object.b();", "global.Object.c; global.Object.c(); new global.Object.c();", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["Object", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["Object", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should iterate the member references of a given global variable, with 'globalThis'.", code: [ "/*global globalThis */", "globalThis.Object.a;", "globalThis.Object.b; globalThis.Object.b(); new globalThis.Object.b();", "globalThis.Object.c; globalThis.Object.c(); new globalThis.Object.c();", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["Object", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["Object", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should iterate the member references of a given global variable, with 'self'.", code: [ "/*global self */", "self.Object.a;", "self.Object.b; self.Object.b(); new self.Object.b();", "self.Object.c; self.Object.c(); new self.Object.c();", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["Object", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["Object", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should iterate the member references of a given global variable, with 'window'.", code: [ "/*global window */", "window.Object.a;", "window.Object.b; window.Object.b(); new window.Object.b();", "window.Object.c; window.Object.c(); new window.Object.c();", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "MemberExpression" }, path: ["Object", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["Object", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["Object", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should not iterate the references of a given global variable if it's modified.", code: [ "Object = {}", "Object.a", "Object.b()", "new Object.c()", ].join("\n"), traceMap: { Object: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [], }, { description: "should not iterate the references through unary/binary expressions.", code: [ 'var construct = typeof Reflect !== "undefined" ? Reflect.construct : undefined', "construct()", ].join("\n"), traceMap: { Reflect: { [CALL]: 1 }, }, expected: [], }, ]) { it(description, () => { const linter = new eslint.Linter() let actual = null linter.defineRule("test", context => ({ "Program:exit"() { const tracker = new ReferenceTracker(context.getScope()) actual = Array.from( tracker.iterateGlobalReferences(traceMap) ).map(x => Object.assign(x, { node: Object.assign( { type: x.node.type }, x.node.optional ? { optional: x.node.optional } : {} ), }) ) }, })) linter.verify(code, config) assert.deepStrictEqual(actual, expected) }) } }) describe("the 'iterateCjsReferences' method", () => { for (const { description, code, traceMap, expected } of [ { description: "should iterate the references of a given CJS modules.", code: [ "/*global require */", "const abc = require('abc');", "abc();", "new abc();", "abc.xyz;", ...(semver.gte(eslint.CLIEngine.version, "6.0.0") ? [ "abc?.xyz;", "abc?.();", "abc?.xyz?.();", "(abc.def).ghi;", "(abc?.def)?.ghi;", ] : []), ].join("\n"), traceMap: { abc: { [READ]: 1, [CALL]: 2, [CONSTRUCT]: 3, xyz: { [READ]: 4 }, def: { ghi: { [READ]: 5 } }, }, }, expected: [ { node: { type: "CallExpression" }, path: ["abc"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["abc"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["abc"], type: CONSTRUCT, info: 3, }, { node: { type: "MemberExpression" }, path: ["abc", "xyz"], type: READ, info: 4, }, ...(semver.gte(eslint.CLIEngine.version, "6.0.0") ? [ { node: { type: "MemberExpression", optional: true, }, path: ["abc", "xyz"], type: READ, info: 4, }, { node: { type: "CallExpression", optional: true, }, path: ["abc"], type: CALL, info: 2, }, { node: { type: "MemberExpression", optional: true, }, path: ["abc", "xyz"], type: READ, info: 4, }, { node: { type: "MemberExpression" }, path: ["abc", "def", "ghi"], type: READ, info: 5, }, { node: { type: "MemberExpression", optional: true, }, path: ["abc", "def", "ghi"], type: READ, info: 5, }, ] : []), ], }, { description: "should NOT iterate the references of a given CJS modules if the 'require' variable wasn't defined.", code: [ "const abc = require('abc');", "abc();", "new abc();", "abc.xyz;", ].join("\n"), traceMap: { abc: { [READ]: 1, [CALL]: 2, [CONSTRUCT]: 3, xyz: { [READ]: 4 }, }, }, expected: [], }, { description: "should NOT iterate the references of a given CJS modules if the 'require' variable was overrided.", code: [ "/*global require */", "const require = () => {};", "const abc = require('abc');", "abc();", "new abc();", "abc.xyz;", ].join("\n"), traceMap: { abc: { [READ]: 1, [CALL]: 2, [CONSTRUCT]: 3, xyz: { [READ]: 4 }, }, }, expected: [], }, ]) { it(description, () => { const linter = new eslint.Linter() let actual = null linter.defineRule("test", context => ({ "Program:exit"() { const tracker = new ReferenceTracker(context.getScope()) actual = Array.from( tracker.iterateCjsReferences(traceMap) ).map(x => Object.assign(x, { node: Object.assign( { type: x.node.type }, x.node.optional ? { optional: x.node.optional } : {} ), }) ) }, })) linter.verify(code, config) assert.deepStrictEqual(actual, expected) }) } }) describe("the 'iterateEsmReferences' method", () => { for (const { description, code, traceMap, expected } of [ { description: "should iterate the references of a given ES modules (with CJS module and the default export).", code: [ "import abc from 'abc';", "abc();", "new abc();", "abc.xyz;", ].join("\n"), traceMap: { abc: { [READ]: 1, [CALL]: 2, [CONSTRUCT]: 3, xyz: { [READ]: 4 }, }, }, expected: [ { node: { type: "ImportDeclaration" }, path: ["abc"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["abc"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["abc"], type: CONSTRUCT, info: 3, }, { node: { type: "MemberExpression" }, path: ["abc", "xyz"], type: READ, info: 4, }, ], }, { description: "should map CJS module to the default export.", code: [ "import {default as x} from 'abc';", "x.a;", "x.b();", "new x.c();", ].join("\n"), traceMap: { abc: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "MemberExpression" }, path: ["abc", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["abc", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["abc", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should NOT map CJS module to the named exports.", code: [ "import {a, b, c} from 'abc';", "a;", "b();", "new c();", ].join("\n"), traceMap: { abc: { a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [], }, { description: "should iterate the references of a given ES modules.", code: [ "import x, {a, b, c, y} from 'abc';", "x.a;", "x.y;", "a;", "b();", "new c();", ].join("\n"), traceMap: { abc: { [ESM]: true, default: { y: { [READ]: 4 }, }, a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "MemberExpression" }, path: ["abc", "default", "y"], type: READ, info: 4, }, { node: { type: "ImportSpecifier" }, path: ["abc", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["abc", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["abc", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should iterate the references of a given ES modules, with ImportNamespaceSpecifier.", code: [ "import * as x from 'abc';", "x.default.a;", "x.default.y;", "x.a;", "x.b();", "new x.c();", ].join("\n"), traceMap: { abc: { [ESM]: true, default: { y: { [READ]: 4 }, }, a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, }, }, expected: [ { node: { type: "MemberExpression" }, path: ["abc", "default", "y"], type: READ, info: 4, }, { node: { type: "MemberExpression" }, path: ["abc", "a"], type: READ, info: 1, }, { node: { type: "CallExpression" }, path: ["abc", "b"], type: CALL, info: 2, }, { node: { type: "NewExpression" }, path: ["abc", "c"], type: CONSTRUCT, info: 3, }, ], }, { description: "should iterate the references of a given ES modules, with ExportNamedDeclaration.", code: "export {a, b, c} from 'abc';", traceMap: { abc: { [ESM]: true, default: { y: { [READ]: 4 }, }, a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, d: { [READ]: 5 }, }, }, expected: [ { node: { type: "ExportSpecifier" }, path: ["abc", "a"], type: READ, info: 1, }, ], }, { description: "should iterate the references of a given ES modules, with ExportAllDeclaration.", code: "export * from 'abc';", traceMap: { abc: { [ESM]: true, default: { y: { [READ]: 4 }, }, a: { [READ]: 1 }, b: { [CALL]: 2 }, c: { [CONSTRUCT]: 3 }, d: { [READ]: 5 }, }, }, expected: [ { node: { type: "ExportAllDeclaration" }, path: ["abc", "a"], type: READ, info: 1, }, { node: { type: "ExportAllDeclaration" }, path: ["abc", "d"], type: READ, info: 5, }, ], }, ]) { it(description, () => { const linter = new eslint.Linter() let actual = null linter.defineRule("test", context => ({ "Program:exit"() { const tracker = new ReferenceTracker(context.getScope()) actual = Array.from( tracker.iterateEsmReferences(traceMap) ).map(x => Object.assign(x, { node: Object.assign( { type: x.node.type }, x.node.optional ? { optional: x.node.optional } : {} ), }) ) }, })) linter.verify(code, config) assert.deepStrictEqual(actual, expected) }) } }) }) eslint-utils-2.1.0/test/token-predicate.js000066400000000000000000000124271367166352100205610ustar00rootroot00000000000000import assert from "assert" import { isArrowToken, isClosingBraceToken, isClosingBracketToken, isClosingParenToken, isColonToken, isCommaToken, isCommentToken, isNotArrowToken, isNotClosingBraceToken, isNotClosingBracketToken, isNotClosingParenToken, isNotColonToken, isNotCommaToken, isNotCommentToken, isNotOpeningBraceToken, isNotOpeningBracketToken, isNotOpeningParenToken, isNotSemicolonToken, isOpeningBraceToken, isOpeningBracketToken, isOpeningParenToken, isSemicolonToken, } from "../src/" describe("The predicate functions for tokens", () => { for (const { positive, negative, patterns } of [ { positive: isArrowToken, negative: isNotArrowToken, patterns: [ [{ type: "Punctuator", value: "=>" }, true], [{ type: "Punctuator", value: ">" }, false], [{ type: "Line", value: "=>" }, false], ], }, { positive: isClosingBraceToken, negative: isNotClosingBraceToken, patterns: [ [{ type: "Punctuator", value: "}" }, true], [{ type: "Punctuator", value: "{" }, false], [{ type: "Punctuator", value: ")" }, false], [{ type: "Line", value: "}" }, false], ], }, { positive: isClosingBracketToken, negative: isNotClosingBracketToken, patterns: [ [{ type: "Punctuator", value: "]" }, true], [{ type: "Punctuator", value: "[" }, false], [{ type: "Punctuator", value: ")" }, false], [{ type: "Line", value: "]" }, false], ], }, { positive: isClosingParenToken, negative: isNotClosingParenToken, patterns: [ [{ type: "Punctuator", value: ")" }, true], [{ type: "Punctuator", value: "(" }, false], [{ type: "Punctuator", value: "}" }, false], [{ type: "Line", value: ")" }, false], ], }, { positive: isColonToken, negative: isNotColonToken, patterns: [ [{ type: "Punctuator", value: ":" }, true], [{ type: "Punctuator", value: ";" }, false], [{ type: "Line", value: ":" }, false], ], }, { positive: isCommaToken, negative: isNotCommaToken, patterns: [ [{ type: "Punctuator", value: "," }, true], [{ type: "Punctuator", value: "." }, false], [{ type: "Line", value: "," }, false], ], }, { positive: isCommentToken, negative: isNotCommentToken, patterns: [ [{ type: "Line", value: "." }, true], [{ type: "Block", value: "." }, true], [{ type: "Shebang", value: "." }, true], [{ type: "Punctuator", value: "." }, false], ], }, { positive: isOpeningBraceToken, negative: isNotOpeningBraceToken, patterns: [ [{ type: "Punctuator", value: "{" }, true], [{ type: "Punctuator", value: "(" }, false], [{ type: "Punctuator", value: "}" }, false], [{ type: "Line", value: "{" }, false], ], }, { positive: isOpeningBracketToken, negative: isNotOpeningBracketToken, patterns: [ [{ type: "Punctuator", value: "[" }, true], [{ type: "Punctuator", value: "(" }, false], [{ type: "Punctuator", value: "]" }, false], [{ type: "Line", value: "[" }, false], ], }, { positive: isOpeningParenToken, negative: isNotOpeningParenToken, patterns: [ [{ type: "Punctuator", value: "(" }, true], [{ type: "Punctuator", value: "{" }, false], [{ type: "Punctuator", value: ")" }, false], [{ type: "Line", value: "(" }, false], ], }, { positive: isSemicolonToken, negative: isNotSemicolonToken, patterns: [ [{ type: "Punctuator", value: ";" }, true], [{ type: "Punctuator", value: ":" }, false], [{ type: "Line", value: ";" }, false], ], }, ]) { const baseName = positive.name.slice(2) describe(`'is${baseName}'`, () => { for (const [token, expected] of patterns) { it(`should return ${expected} if ${JSON.stringify( token )} was given.`, () => { assert.strictEqual(positive(token), expected) }) } }) describe(`'isNot${baseName}'`, () => { for (const [token, expected] of patterns) { it(`should return ${!expected} if ${JSON.stringify( token )} was given.`, () => { assert.strictEqual(negative(token), !expected) }) } }) } })