Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
github
/
codeql-action
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Issues
0
Pull requests
0
Actions
Projects
0
Security
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Security
Insights
Files
9bfb9ba
.github
.vscode
analyze
autobuild
init
lib
node_modules
.bin
@aashutoshrathi
@actions
@ava
@azure
@babel
@chrisgavin
@eslint-community
@eslint
@github
@humanwhocodes
@nodelib
@octokit
@opentelemetry
@pkgr
@schemastore
@sinonjs
@types
@typescript-eslint
abort-controller
acorn-jsx
acorn-walk
acorn
adm-zip
aggregate-error
ajv
ansi-regex
ansi-styles
anymatch
argparse
aria-query
array-buffer-byte-length
array-find-index
array-includes
array-union
array.prototype.findlastindex
array.prototype.flat
array.prototype.flatmap
arraybuffer.prototype.slice
arrgv
ast-types-flow
asynckit
ava
available-typed-arrays
axe-core
axobject-query
balanced-match
before-after-hook
big-integer
binary-extensions
blueimp-md5
bottleneck
bplist-parser
brace-expansion
braces
browserslist
bundle-name
call-bind
callsites
caniuse-lite
cbor
chalk
check-disk-space
chokidar
chunkd
ci-info
ci-parallel-vars
clean-stack
clean-yaml-object
cli-truncate
cliui
code-excerpt
color-convert
color-name
combined-stream
common-path-prefix
concat-map
concordance
console-log-level
convert-to-spaces
cross-spawn
currently-unhandled
damerau-levenshtein
date-time
debug
deep-equal
deep-is
default-browser-id
default-browser
define-lazy-prop
define-properties
del
delayed-stream
deprecation
dequal
diff
dir-glob
doctrine
eastasianwidth
electron-to-chromium
emittery
emoji-regex
enhanced-resolve
es-abstract
es-get-iterator
es-set-tostringtag
es-shim-unscopables
es-to-primitive
escalade
escape-string-regexp
eslint-config-prettier
eslint-import-resolver-node
eslint-import-resolver-typescript
eslint-module-utils
eslint-plugin-escompat
eslint-plugin-eslint-comments
eslint-plugin-filenames
eslint-plugin-github
eslint-plugin-i18n-text
eslint-plugin-import
eslint-plugin-jsx-a11y
eslint-plugin-no-async-foreach
eslint-plugin-no-only-tests
eslint-plugin-prettier
eslint-rule-documentation
eslint-scope
eslint-visitor-keys
eslint
bin
conf
lib
cli-engine
config
eslint
linter
rule-tester
rules
utils
accessor-pairs.js
array-bracket-newline.js
array-bracket-spacing.js
array-callback-return.js
array-element-newline.js
arrow-body-style.js
arrow-parens.js
arrow-spacing.js
block-scoped-var.js
block-spacing.js
brace-style.js
callback-return.js
camelcase.js
capitalized-comments.js
class-methods-use-this.js
comma-dangle.js
comma-spacing.js
comma-style.js
complexity.js
computed-property-spacing.js
consistent-return.js
consistent-this.js
constructor-super.js
curly.js
default-case-last.js
default-case.js
default-param-last.js
dot-location.js
dot-notation.js
eol-last.js
eqeqeq.js
for-direction.js
func-call-spacing.js
func-name-matching.js
func-names.js
func-style.js
function-call-argument-newline.js
function-paren-newline.js
generator-star-spacing.js
getter-return.js
global-require.js
grouped-accessor-pairs.js
guard-for-in.js
handle-callback-err.js
id-blacklist.js
id-denylist.js
id-length.js
id-match.js
implicit-arrow-linebreak.js
indent-legacy.js
indent.js
index.js
init-declarations.js
jsx-quotes.js
key-spacing.js
keyword-spacing.js
line-comment-position.js
linebreak-style.js
lines-around-comment.js
lines-around-directive.js
lines-between-class-members.js
logical-assignment-operators.js
max-classes-per-file.js
max-depth.js
max-len.js
max-lines-per-function.js
max-lines.js
max-nested-callbacks.js
max-params.js
max-statements-per-line.js
max-statements.js
multiline-comment-style.js
multiline-ternary.js
new-cap.js
new-parens.js
newline-after-var.js
newline-before-return.js
newline-per-chained-call.js
no-alert.js
no-array-constructor.js
no-async-promise-executor.js
no-await-in-loop.js
no-bitwise.js
no-buffer-constructor.js
no-caller.js
no-case-declarations.js
no-catch-shadow.js
no-class-assign.js
no-compare-neg-zero.js
no-cond-assign.js
no-confusing-arrow.js
no-console.js
no-const-assign.js
no-constant-binary-expression.js
no-constant-condition.js
no-constructor-return.js
no-continue.js
no-control-regex.js
no-debugger.js
no-delete-var.js
no-div-regex.js
no-dupe-args.js
no-dupe-class-members.js
no-dupe-else-if.js
no-dupe-keys.js
no-duplicate-case.js
no-duplicate-imports.js
no-else-return.js
no-empty-character-class.js
no-empty-function.js
no-empty-pattern.js
no-empty-static-block.js
no-empty.js
no-eq-null.js
no-eval.js
no-ex-assign.js
no-extend-native.js
no-extra-bind.js
no-extra-boolean-cast.js
no-extra-label.js
no-extra-parens.js
no-extra-semi.js
no-fallthrough.js
no-floating-decimal.js
no-func-assign.js
no-global-assign.js
no-implicit-coercion.js
no-implicit-globals.js
no-implied-eval.js
no-import-assign.js
no-inline-comments.js
no-inner-declarations.js
no-invalid-regexp.js
no-invalid-this.js
no-irregular-whitespace.js
no-iterator.js
no-label-var.js
no-labels.js
no-lone-blocks.js
no-lonely-if.js
no-loop-func.js
no-loss-of-precision.js
no-magic-numbers.js
no-misleading-character-class.js
no-mixed-operators.js
no-mixed-requires.js
no-mixed-spaces-and-tabs.js
no-multi-assign.js
no-multi-spaces.js
no-multi-str.js
no-multiple-empty-lines.js
no-native-reassign.js
no-negated-condition.js
no-negated-in-lhs.js
no-nested-ternary.js
no-new-func.js
no-new-native-nonconstructor.js
no-new-object.js
no-new-require.js
no-new-symbol.js
no-new-wrappers.js
no-new.js
no-nonoctal-decimal-escape.js
no-obj-calls.js
no-octal-escape.js
no-octal.js
no-param-reassign.js
no-path-concat.js
no-plusplus.js
no-process-env.js
no-process-exit.js
no-promise-executor-return.js
no-proto.js
no-prototype-builtins.js
no-redeclare.js
no-regex-spaces.js
no-restricted-exports.js
no-restricted-globals.js
no-restricted-imports.js
no-restricted-modules.js
no-restricted-properties.js
no-restricted-syntax.js
no-return-assign.js
no-return-await.js
no-script-url.js
no-self-assign.js
no-self-compare.js
no-sequences.js
no-setter-return.js
no-shadow-restricted-names.js
no-shadow.js
no-spaced-func.js
no-sparse-arrays.js
no-sync.js
no-tabs.js
no-template-curly-in-string.js
no-ternary.js
no-this-before-super.js
no-throw-literal.js
no-trailing-spaces.js
no-undef-init.js
no-undef.js
no-undefined.js
no-underscore-dangle.js
no-unexpected-multiline.js
no-unmodified-loop-condition.js
no-unneeded-ternary.js
no-unreachable-loop.js
no-unreachable.js
no-unsafe-finally.js
no-unsafe-negation.js
no-unsafe-optional-chaining.js
no-unused-expressions.js
no-unused-labels.js
no-unused-private-class-members.js
no-unused-vars.js
no-use-before-define.js
no-useless-backreference.js
no-useless-call.js
no-useless-catch.js
no-useless-computed-key.js
no-useless-concat.js
no-useless-constructor.js
no-useless-escape.js
no-useless-rename.js
no-useless-return.js
no-var.js
no-void.js
no-warning-comments.js
no-whitespace-before-property.js
no-with.js
nonblock-statement-body-position.js
object-curly-newline.js
object-curly-spacing.js
object-property-newline.js
object-shorthand.js
one-var-declaration-per-line.js
one-var.js
operator-assignment.js
operator-linebreak.js
padded-blocks.js
padding-line-between-statements.js
prefer-arrow-callback.js
prefer-const.js
prefer-destructuring.js
prefer-exponentiation-operator.js
prefer-named-capture-group.js
prefer-numeric-literals.js
prefer-object-has-own.js
prefer-object-spread.js
prefer-promise-reject-errors.js
prefer-reflect.js
prefer-regex-literals.js
prefer-rest-params.js
prefer-spread.js
prefer-template.js
quote-props.js
quotes.js
radix.js
require-atomic-updates.js
require-await.js
require-jsdoc.js
require-unicode-regexp.js
require-yield.js
rest-spread-spacing.js
semi-spacing.js
semi-style.js
semi.js
sort-imports.js
sort-keys.js
sort-vars.js
space-before-blocks.js
space-before-function-paren.js
space-in-parens.js
space-infix-ops.js
space-unary-ops.js
spaced-comment.js
strict.js
switch-colon-spacing.js
symbol-description.js
template-curly-spacing.js
template-tag-spacing.js
unicode-bom.js
use-isnan.js
valid-jsdoc.js
valid-typeof.js
vars-on-top.js
wrap-iife.js
wrap-regex.js
yield-star-spacing.js
yoda.js
shared
source-code
api.js
cli.js
options.js
unsupported-api.js
messages
node_modules
LICENSE
README.md
package.json
espree
esprima
esquery
esrecurse
estraverse
esutils
event-target-shim
events
execa
fast-deep-equal
fast-diff
fast-glob
fast-json-stable-stringify
fast-levenshtein
fastq
figures
file-entry-cache
file-url
fill-range
find-up
flat-cache
flatted
for-each
form-data
fs.realpath
fs
fsevents
function-bind
function.prototype.name
functions-have-names
gar
get-caller-file
get-folder-size
get-intrinsic
get-stream
get-symbol-description
get-tsconfig
glob-parent
globals
globalthis
globby
gopd
graceful-fs
graphemer
has-bigints
has-property-descriptors
has-proto
has-symbols
has-tostringtag
has
human-signals
ignore-by-default
ignore
import-fresh
imurmurhash
indent-string
inflight
inherits
internal-slot
irregular-plurals
is-arguments
is-array-buffer
is-bigint
is-binary-path
is-boolean-object
is-callable
is-core-module
is-date-object
is-docker
is-error
is-extglob
is-fullwidth-code-point
is-glob
is-inside-container
is-map
is-negative-zero
is-number-object
is-number
is-path-cwd
is-path-inside
is-plain-object
is-promise
is-regex
is-set
is-shared-array-buffer
is-stream
is-string
is-symbol
is-typed-array
is-unicode-supported
is-weakmap
is-weakref
is-weakset
is-wsl
isarray
isexe
js-string-escape
js-yaml
json-schema-traverse
json-stable-stringify-without-jsonify
json-stringify-safe
json5
jsonschema
jsx-ast-utils
just-extend
language-subtag-registry
language-tags
levn
load-json-file
locate-path
lodash.camelcase
lodash.get
lodash.kebabcase
lodash.merge
lodash.snakecase
lodash.upperfirst
lodash
long
lru-cache
map-age-cleaner
matcher
md5-hex
mem
merge-stream
merge2
micromatch
mime-db
mime-types
mimic-fn
minimatch
minimist
ms
natural-compare
nise
nock
node-fetch
node-releases
nofilter
normalize-path
npm-run-path
object-inspect
object-is
object-keys
object.assign
object.entries
object.fromentries
object.groupby
object.values
once
onetime
open
optionator
p-defer
p-event
p-limit
p-locate
p-map
p-timeout
parent-module
parse-ms
path-exists
path-is-absolute
path-key
path-parse
path-to-regexp
path-type
path
picocolors
picomatch
pkg-conf
plur
prelude-ls
prettier-linter-helpers
prettier
pretty-ms
process
propagate
punycode
queue-microtask
readdirp
regenerator-runtime
regexp.prototype.flags
removeNPMAbsolutePaths
require-directory
requireindex
resolve-cwd
resolve-from
resolve-pkg-maps
resolve
reusify
rimraf
run-applescript
run-parallel
safe-array-concat
safe-regex-test
sax
semver
serialize-error
shebang-command
shebang-regex
side-channel
signal-exit
sinon
slash
slice-ansi
sprintf-js
stack-utils
stop-iteration-iterator
string-width
string.prototype.trim
string.prototype.trimend
string.prototype.trimstart
strip-ansi
strip-bom
strip-final-newline
strip-json-comments
supertap
supports-preserve-symlinks-flag
svg-element-attributes
synckit
tapable
temp-dir
text-table
time-zone
tiny-each-async
titleize
tmp-promise
tmp
to-regex-range
tr46
ts-api-utils
tsconfig-paths
tslib
tunnel
type-check
type-detect
type-fest
typed-array-buffer
typed-array-byte-length
typed-array-byte-offset
typed-array-length
typescript
unbox-primitive
universal-user-agent
untildify
update-browserslist-db
uri-js
util
uuid
webidl-conversions
well-known-symbols
whatwg-url
which-boxed-primitive
which-collection
which-typed-array
which
wrap-ansi
wrappy
write-file-atomic
xml2js
xmlbuilder
y18n
yallist
yargs-parser
yargs
yocto-queue
zlib
.package-lock.json
pr-checks
python-setup
queries
resolve-environment
src
tests
upload-sarif
.editorconfig
.eslintignore
.eslintrc.json
.git-blame-ignore-revs
.gitattributes
.gitignore
.npmrc
CHANGELOG.md
CODEOWNERS
CODE_OF_CONDUCT.md
CONTRIBUTING.md
LICENSE
README.md
package-lock.json
package.json
tsconfig.json
Breadcrumbs
codeql-action
/
node_modules
/
eslint
/
lib
/
rules
/
object-shorthand.js
Blame
Blame
Latest commit
github-actions[bot]
Update checked-in dependencies
Jul 13, 2023
40a500c
·
Jul 13, 2023
History
History
520 lines (450 loc) · 21.8 KB
Breadcrumbs
codeql-action
/
node_modules
/
eslint
/
lib
/
rules
/
object-shorthand.js
Top
File metadata and controls
Code
Blame
520 lines (450 loc) · 21.8 KB
Raw
/** * @fileoverview Rule to enforce concise object methods and properties. * @author Jamund Ferguson */ "use strict"; const OPTIONS = { always: "always", never: "never", methods: "methods", properties: "properties", consistent: "consistent", consistentAsNeeded: "consistent-as-needed" }; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { meta: { type: "suggestion", docs: { description: "Require or disallow method and property shorthand syntax for object literals", recommended: false, url: "https://eslint.org/docs/latest/rules/object-shorthand" }, fixable: "code", schema: { anyOf: [ { type: "array", items: [ { enum: ["always", "methods", "properties", "never", "consistent", "consistent-as-needed"] } ], minItems: 0, maxItems: 1 }, { type: "array", items: [ { enum: ["always", "methods", "properties"] }, { type: "object", properties: { avoidQuotes: { type: "boolean" } }, additionalProperties: false } ], minItems: 0, maxItems: 2 }, { type: "array", items: [ { enum: ["always", "methods"] }, { type: "object", properties: { ignoreConstructors: { type: "boolean" }, methodsIgnorePattern: { type: "string" }, avoidQuotes: { type: "boolean" }, avoidExplicitReturnArrows: { type: "boolean" } }, additionalProperties: false } ], minItems: 0, maxItems: 2 } ] }, messages: { expectedAllPropertiesShorthanded: "Expected shorthand for all properties.", expectedLiteralMethodLongform: "Expected longform method syntax for string literal keys.", expectedPropertyShorthand: "Expected property shorthand.", expectedPropertyLongform: "Expected longform property syntax.", expectedMethodShorthand: "Expected method shorthand.", expectedMethodLongform: "Expected longform method syntax.", unexpectedMix: "Unexpected mix of shorthand and non-shorthand properties." } }, create(context) { const APPLY = context.options[0] || OPTIONS.always; const APPLY_TO_METHODS = APPLY === OPTIONS.methods || APPLY === OPTIONS.always; const APPLY_TO_PROPS = APPLY === OPTIONS.properties || APPLY === OPTIONS.always; const APPLY_NEVER = APPLY === OPTIONS.never; const APPLY_CONSISTENT = APPLY === OPTIONS.consistent; const APPLY_CONSISTENT_AS_NEEDED = APPLY === OPTIONS.consistentAsNeeded; const PARAMS = context.options[1] || {}; const IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors; const METHODS_IGNORE_PATTERN = PARAMS.methodsIgnorePattern ? new RegExp(PARAMS.methodsIgnorePattern, "u") : null; const AVOID_QUOTES = PARAMS.avoidQuotes; const AVOID_EXPLICIT_RETURN_ARROWS = !!PARAMS.avoidExplicitReturnArrows; const sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- const CTOR_PREFIX_REGEX = /[^_$0-9]/u; /** * Determines if the first character of the name is a capital letter. * @param {string} name The name of the node to evaluate. * @returns {boolean} True if the first character of the property name is a capital letter, false if not. * @private */ function isConstructor(name) { const match = CTOR_PREFIX_REGEX.exec(name); // Not a constructor if name has no characters apart from '_', '$' and digits e.g. '_', '$$', '_8' if (!match) { return false; } const firstChar = name.charAt(match.index); return firstChar === firstChar.toUpperCase(); } /** * Determines if the property can have a shorthand form. * @param {ASTNode} property Property AST node * @returns {boolean} True if the property can have a shorthand form * @private */ function canHaveShorthand(property) { return (property.kind !== "set" && property.kind !== "get" && property.type !== "SpreadElement" && property.type !== "SpreadProperty" && property.type !== "ExperimentalSpreadProperty"); } /** * Checks whether a node is a string literal. * @param {ASTNode} node Any AST node. * @returns {boolean} `true` if it is a string literal. */ function isStringLiteral(node) { return node.type === "Literal" && typeof node.value === "string"; } /** * Determines if the property is a shorthand or not. * @param {ASTNode} property Property AST node * @returns {boolean} True if the property is considered shorthand, false if not. * @private */ function isShorthand(property) { // property.method is true when `{a(){}}`. return (property.shorthand || property.method); } /** * Determines if the property's key and method or value are named equally. * @param {ASTNode} property Property AST node * @returns {boolean} True if the key and value are named equally, false if not. * @private */ function isRedundant(property) { const value = property.value; if (value.type === "FunctionExpression") { return !value.id; // Only anonymous should be shorthand method. } if (value.type === "Identifier") { return astUtils.getStaticPropertyName(property) === value.name; } return false; } /** * Ensures that an object's properties are consistently shorthand, or not shorthand at all. * @param {ASTNode} node Property AST node * @param {boolean} checkRedundancy Whether to check longform redundancy * @returns {void} */ function checkConsistency(node, checkRedundancy) { // We are excluding getters/setters and spread properties as they are considered neither longform nor shorthand. const properties = node.properties.filter(canHaveShorthand); // Do we still have properties left after filtering the getters and setters? if (properties.length > 0) { const shorthandProperties = properties.filter(isShorthand); /* * If we do not have an equal number of longform properties as * shorthand properties, we are using the annotations inconsistently */ if (shorthandProperties.length !== properties.length) { // We have at least 1 shorthand property if (shorthandProperties.length > 0) { context.report({ node, messageId: "unexpectedMix" }); } else if (checkRedundancy) { /* * If all properties of the object contain a method or value with a name matching it's key, * all the keys are redundant. */ const canAlwaysUseShorthand = properties.every(isRedundant); if (canAlwaysUseShorthand) { context.report({ node, messageId: "expectedAllPropertiesShorthanded" }); } } } } } /** * Fixes a FunctionExpression node by making it into a shorthand property. * @param {SourceCodeFixer} fixer The fixer object * @param {ASTNode} node A `Property` node that has a `FunctionExpression` or `ArrowFunctionExpression` as its value * @returns {Object} A fix for this node */ function makeFunctionShorthand(fixer, node) { const firstKeyToken = node.computed ? sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken) : sourceCode.getFirstToken(node.key); const lastKeyToken = node.computed ? sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken) : sourceCode.getLastToken(node.key); const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]); let keyPrefix = ""; // key: /* */ () => {} if (sourceCode.commentsExistBetween(lastKeyToken, node.value)) { return null; } if (node.value.async) { keyPrefix += "async "; } if (node.value.generator) { keyPrefix += "*"; } const fixRange = [firstKeyToken.range[0], node.range[1]]; const methodPrefix = keyPrefix + keyText; if (node.value.type === "FunctionExpression") { const functionToken = sourceCode.getTokens(node.value).find(token => token.type === "Keyword" && token.value === "function"); const tokenBeforeParams = node.value.generator ? sourceCode.getTokenAfter(functionToken) : functionToken; return fixer.replaceTextRange( fixRange, methodPrefix + sourceCode.text.slice(tokenBeforeParams.range[1], node.value.range[1]) ); } const arrowToken = sourceCode.getTokenBefore(node.value.body, astUtils.isArrowToken); const fnBody = sourceCode.text.slice(arrowToken.range[1], node.value.range[1]); let shouldAddParensAroundParameters = false; let tokenBeforeParams; if (node.value.params.length === 0) { tokenBeforeParams = sourceCode.getFirstToken(node.value, astUtils.isOpeningParenToken); } else { tokenBeforeParams = sourceCode.getTokenBefore(node.value.params[0]); } if (node.value.params.length === 1) { const hasParen = astUtils.isOpeningParenToken(tokenBeforeParams); const isTokenOutsideNode = tokenBeforeParams.range[0] < node.range[0]; shouldAddParensAroundParameters = !hasParen || isTokenOutsideNode; } const sliceStart = shouldAddParensAroundParameters ? node.value.params[0].range[0] : tokenBeforeParams.range[0]; const sliceEnd = sourceCode.getTokenBefore(arrowToken).range[1]; const oldParamText = sourceCode.text.slice(sliceStart, sliceEnd); const newParamText = shouldAddParensAroundParameters ? `(${oldParamText})` : oldParamText; return fixer.replaceTextRange( fixRange, methodPrefix + newParamText + fnBody ); } /** * Fixes a FunctionExpression node by making it into a longform property. * @param {SourceCodeFixer} fixer The fixer object * @param {ASTNode} node A `Property` node that has a `FunctionExpression` as its value * @returns {Object} A fix for this node */ function makeFunctionLongform(fixer, node) { const firstKeyToken = node.computed ? sourceCode.getTokens(node).find(token => token.value === "[") : sourceCode.getFirstToken(node.key); const lastKeyToken = node.computed ? sourceCode.getTokensBetween(node.key, node.value).find(token => token.value === "]") : sourceCode.getLastToken(node.key); const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]); let functionHeader = "function"; if (node.value.async) { functionHeader = `async ${functionHeader}`; } if (node.value.generator) { functionHeader = `${functionHeader}*`; } return fixer.replaceTextRange([node.range[0], lastKeyToken.range[1]], `${keyText}: ${functionHeader}`); } /* * To determine whether a given arrow function has a lexical identifier (`this`, `arguments`, `super`, or `new.target`), * create a stack of functions that define these identifiers (i.e. all functions except arrow functions) as the AST is * traversed. Whenever a new function is encountered, create a new entry on the stack (corresponding to a different lexical * scope of `this`), and whenever a function is exited, pop that entry off the stack. When an arrow function is entered, * keep a reference to it on the current stack entry, and remove that reference when the arrow function is exited. * When a lexical identifier is encountered, mark all the arrow functions on the current stack entry by adding them * to an `arrowsWithLexicalIdentifiers` set. Any arrow function in that set will not be reported by this rule, * because converting it into a method would change the value of one of the lexical identifiers. */ const lexicalScopeStack = []; const arrowsWithLexicalIdentifiers = new WeakSet(); const argumentsIdentifiers = new WeakSet(); /** * Enters a function. This creates a new lexical identifier scope, so a new Set of arrow functions is pushed onto the stack. * Also, this marks all `arguments` identifiers so that they can be detected later. * @param {ASTNode} node The node representing the function. * @returns {void} */ function enterFunction(node) { lexicalScopeStack.unshift(new Set()); sourceCode.getScope(node).variables.filter(variable => variable.name === "arguments").forEach(variable => { variable.references.map(ref => ref.identifier).forEach(identifier => argumentsIdentifiers.add(identifier)); }); } /** * Exits a function. This pops the current set of arrow functions off the lexical scope stack. * @returns {void} */ function exitFunction() { lexicalScopeStack.shift(); } /** * Marks the current function as having a lexical keyword. This implies that all arrow functions * in the current lexical scope contain a reference to this lexical keyword. * @returns {void} */ function reportLexicalIdentifier() { lexicalScopeStack[0].forEach(arrowFunction => arrowsWithLexicalIdentifiers.add(arrowFunction)); } //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- return { Program: enterFunction, FunctionDeclaration: enterFunction, FunctionExpression: enterFunction, "Program:exit": exitFunction, "FunctionDeclaration:exit": exitFunction, "FunctionExpression:exit": exitFunction, ArrowFunctionExpression(node) { lexicalScopeStack[0].add(node); }, "ArrowFunctionExpression:exit"(node) { lexicalScopeStack[0].delete(node); }, ThisExpression: reportLexicalIdentifier, Super: reportLexicalIdentifier, MetaProperty(node) { if (node.meta.name === "new" && node.property.name === "target") { reportLexicalIdentifier(); } }, Identifier(node) { if (argumentsIdentifiers.has(node)) { reportLexicalIdentifier(); } }, ObjectExpression(node) { if (APPLY_CONSISTENT) { checkConsistency(node, false); } else if (APPLY_CONSISTENT_AS_NEEDED) { checkConsistency(node, true); } }, "Property:exit"(node) { const isConciseProperty = node.method || node.shorthand; // Ignore destructuring assignment if (node.parent.type === "ObjectPattern") { return; } // getters and setters are ignored if (node.kind === "get" || node.kind === "set") { return; } // only computed methods can fail the following checks if (node.computed && node.value.type !== "FunctionExpression" && node.value.type !== "ArrowFunctionExpression") { return; } //-------------------------------------------------------------- // Checks for property/method shorthand. if (isConciseProperty) { if (node.method && (APPLY_NEVER || AVOID_QUOTES && isStringLiteral(node.key))) { const messageId = APPLY_NEVER ? "expectedMethodLongform" : "expectedLiteralMethodLongform"; // { x() {} } should be written as { x: function() {} } context.report({ node, messageId, fix: fixer => makeFunctionLongform(fixer, node) }); } else if (APPLY_NEVER) { // { x } should be written as { x: x } context.report({ node, messageId: "expectedPropertyLongform", fix: fixer => fixer.insertTextAfter(node.key, `: ${node.key.name}`) }); } } else if (APPLY_TO_METHODS && !node.value.id && (node.value.type === "FunctionExpression" || node.value.type === "ArrowFunctionExpression")) { if (IGNORE_CONSTRUCTORS && node.key.type === "Identifier" && isConstructor(node.key.name)) { return; } if (METHODS_IGNORE_PATTERN) { const propertyName = astUtils.getStaticPropertyName(node); if (propertyName !== null && METHODS_IGNORE_PATTERN.test(propertyName)) { return; } } if (AVOID_QUOTES && isStringLiteral(node.key)) { return; } // {[x]: function(){}} should be written as {[x]() {}} if (node.value.type === "FunctionExpression" || node.value.type === "ArrowFunctionExpression" && node.value.body.type === "BlockStatement" && AVOID_EXPLICIT_RETURN_ARROWS && !arrowsWithLexicalIdentifiers.has(node.value) ) { context.report({ node, messageId: "expectedMethodShorthand", fix: fixer => makeFunctionShorthand(fixer, node) }); } } else if (node.value.type === "Identifier" && node.key.name === node.value.name && APPLY_TO_PROPS) { // {x: x} should be written as {x} context.report({ node, messageId: "expectedPropertyShorthand", fix(fixer) { return fixer.replaceText(node, node.value.name); } }); } else if (node.value.type === "Identifier" && node.key.type === "Literal" && node.key.value === node.value.name && APPLY_TO_PROPS) { if (AVOID_QUOTES) { return; } // {"x": x} should be written as {x} context.report({ node, messageId: "expectedPropertyShorthand", fix(fixer) { return fixer.replaceText(node, node.value.name); } }); } } }; } };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
You can’t perform that action at this time.