refactor start
This commit is contained in:
parent
bd49791e06
commit
e46d25f0b7
16699 changed files with 2 additions and 1484887 deletions
476
home/ags/node_modules/eslint-plugin-promise/rules/no-multiple-resolved.js
generated
vendored
476
home/ags/node_modules/eslint-plugin-promise/rules/no-multiple-resolved.js
generated
vendored
|
|
@ -1,476 +0,0 @@
|
|||
/**
|
||||
* Rule: no-multiple-resolved
|
||||
* Disallow creating new promises with paths that resolve multiple times
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const { getScope } = require('./lib/eslint-compat')
|
||||
const getDocsUrl = require('./lib/get-docs-url')
|
||||
const {
|
||||
isPromiseConstructorWithInlineExecutor,
|
||||
} = require('./lib/is-promise-constructor')
|
||||
|
||||
/**
|
||||
* @typedef {import('estree').Node} Node
|
||||
* @typedef {import('estree').Expression} Expression
|
||||
* @typedef {import('estree').Identifier} Identifier
|
||||
* @typedef {import('estree').FunctionExpression} FunctionExpression
|
||||
* @typedef {import('estree').ArrowFunctionExpression} ArrowFunctionExpression
|
||||
* @typedef {import('estree').SimpleCallExpression} CallExpression
|
||||
* @typedef {import('estree').MemberExpression} MemberExpression
|
||||
* @typedef {import('estree').NewExpression} NewExpression
|
||||
* @typedef {import('estree').ImportExpression} ImportExpression
|
||||
* @typedef {import('estree').YieldExpression} YieldExpression
|
||||
* @typedef {import('eslint').Rule.CodePath} CodePath
|
||||
* @typedef {import('eslint').Rule.CodePathSegment} CodePathSegment
|
||||
*/
|
||||
|
||||
/**
|
||||
* An expression that can throw an error.
|
||||
* see https://github.com/eslint/eslint/blob/e940be7a83d0caea15b64c1e1c2785a6540e2641/lib/linter/code-path-analysis/code-path-analyzer.js#L639-L643
|
||||
* @typedef {CallExpression | MemberExpression | NewExpression | ImportExpression | YieldExpression} ThrowableExpression
|
||||
*/
|
||||
|
||||
/**
|
||||
* Iterate all previous path segments.
|
||||
* @param {CodePathSegment} segment
|
||||
* @returns {Iterable<CodePathSegment[]>}
|
||||
*/
|
||||
function* iterateAllPrevPathSegments(segment) {
|
||||
yield* iterate(segment, [])
|
||||
|
||||
/**
|
||||
* @param {CodePathSegment} segment
|
||||
* @param {CodePathSegment[]} processed
|
||||
*/
|
||||
function* iterate(segment, processed) {
|
||||
if (processed.includes(segment)) {
|
||||
return
|
||||
}
|
||||
const nextProcessed = [segment, ...processed]
|
||||
|
||||
for (const prev of segment.prevSegments) {
|
||||
if (prev.prevSegments.length === 0) {
|
||||
yield [prev]
|
||||
} else {
|
||||
for (const segments of iterate(prev, nextProcessed)) {
|
||||
yield [prev, ...segments]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Iterate all next path segments.
|
||||
* @param {CodePathSegment} segment
|
||||
* @returns {Iterable<CodePathSegment[]>}
|
||||
*/
|
||||
function* iterateAllNextPathSegments(segment) {
|
||||
yield* iterate(segment, [])
|
||||
|
||||
/**
|
||||
* @param {CodePathSegment} segment
|
||||
* @param {CodePathSegment[]} processed
|
||||
*/
|
||||
function* iterate(segment, processed) {
|
||||
if (processed.includes(segment)) {
|
||||
return
|
||||
}
|
||||
const nextProcessed = [segment, ...processed]
|
||||
|
||||
for (const next of segment.nextSegments) {
|
||||
if (next.nextSegments.length === 0) {
|
||||
yield [next]
|
||||
} else {
|
||||
for (const segments of iterate(next, nextProcessed)) {
|
||||
yield [next, ...segments]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the same route path from the given path following previous path segments.
|
||||
* @param {CodePathSegment} segment
|
||||
* @returns {CodePathSegment | null}
|
||||
*/
|
||||
function findSameRoutePathSegment(segment) {
|
||||
/** @type {Set<CodePathSegment>} */
|
||||
const routeSegments = new Set()
|
||||
for (const route of iterateAllPrevPathSegments(segment)) {
|
||||
if (routeSegments.size === 0) {
|
||||
// First
|
||||
for (const seg of route) {
|
||||
routeSegments.add(seg)
|
||||
}
|
||||
continue
|
||||
}
|
||||
for (const seg of routeSegments) {
|
||||
if (!route.includes(seg)) {
|
||||
routeSegments.delete(seg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const routeSegment of routeSegments) {
|
||||
let hasUnreached = false
|
||||
for (const segments of iterateAllNextPathSegments(routeSegment)) {
|
||||
if (!segments.includes(segment)) {
|
||||
// It has a route that does not reach the given path.
|
||||
hasUnreached = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!hasUnreached) {
|
||||
return routeSegment
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
class CodePathInfo {
|
||||
/**
|
||||
* @param {CodePath} path
|
||||
*/
|
||||
constructor(path) {
|
||||
this.path = path
|
||||
/** @type {Map<CodePathSegment, CodePathSegmentInfo>} */
|
||||
this.segmentInfos = new Map()
|
||||
this.resolvedCount = 0
|
||||
/** @type {CodePathSegment[]} */
|
||||
this.allSegments = []
|
||||
}
|
||||
|
||||
getCurrentSegmentInfos() {
|
||||
return this.path.currentSegments.map((segment) => {
|
||||
const info = this.segmentInfos.get(segment)
|
||||
if (info) {
|
||||
return info
|
||||
}
|
||||
const newInfo = new CodePathSegmentInfo(this, segment)
|
||||
this.segmentInfos.set(segment, newInfo)
|
||||
return newInfo
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @typedef {object} AlreadyResolvedData
|
||||
* @property {Identifier} resolved
|
||||
* @property {'certain' | 'potential'} kind
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check all paths and return paths resolved multiple times.
|
||||
* @param {PromiseCodePathContext} promiseCodePathContext
|
||||
* @returns {Iterable<AlreadyResolvedData & { node: Identifier }>}
|
||||
*/
|
||||
*iterateReports(promiseCodePathContext) {
|
||||
const targets = [...this.segmentInfos.values()].filter(
|
||||
(info) => info.resolved
|
||||
)
|
||||
for (const segmentInfo of targets) {
|
||||
const result = this._getAlreadyResolvedData(
|
||||
segmentInfo.segment,
|
||||
promiseCodePathContext
|
||||
)
|
||||
if (result) {
|
||||
yield {
|
||||
node: segmentInfo.resolved,
|
||||
resolved: result.resolved,
|
||||
kind: result.kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Compute the previously resolved path.
|
||||
* @param {CodePathSegment} segment
|
||||
* @param {PromiseCodePathContext} promiseCodePathContext
|
||||
* @returns {AlreadyResolvedData | null}
|
||||
*/
|
||||
_getAlreadyResolvedData(segment, promiseCodePathContext) {
|
||||
const prevSegments = segment.prevSegments.filter(
|
||||
(prev) => !promiseCodePathContext.isResolvedTryBlockCodePathSegment(prev)
|
||||
)
|
||||
if (prevSegments.length === 0) {
|
||||
return null
|
||||
}
|
||||
const prevSegmentInfos = prevSegments.map((prev) =>
|
||||
this._getProcessedSegmentInfo(prev, promiseCodePathContext)
|
||||
)
|
||||
if (prevSegmentInfos.every((info) => info.resolved)) {
|
||||
// If the previous paths are all resolved, the next path is also resolved.
|
||||
return {
|
||||
resolved: prevSegmentInfos[0].resolved,
|
||||
kind: 'certain',
|
||||
}
|
||||
}
|
||||
|
||||
for (const prevSegmentInfo of prevSegmentInfos) {
|
||||
if (prevSegmentInfo.resolved) {
|
||||
// If the previous path is partially resolved,
|
||||
// then the next path is potentially resolved.
|
||||
return {
|
||||
resolved: prevSegmentInfo.resolved,
|
||||
kind: 'potential',
|
||||
}
|
||||
}
|
||||
if (prevSegmentInfo.potentiallyResolved) {
|
||||
let potential = false
|
||||
if (prevSegmentInfo.segment.nextSegments.length === 1) {
|
||||
// If the previous path is potentially resolved and there is one next path,
|
||||
// then the next path is potentially resolved.
|
||||
potential = true
|
||||
} else {
|
||||
// This is necessary, for example, if `resolve()` in the finally section.
|
||||
const segmentInfo = this.segmentInfos.get(segment)
|
||||
if (segmentInfo && segmentInfo.resolved) {
|
||||
if (
|
||||
prevSegmentInfo.segment.nextSegments.every((next) => {
|
||||
const nextSegmentInfo = this.segmentInfos.get(next)
|
||||
return (
|
||||
nextSegmentInfo &&
|
||||
nextSegmentInfo.resolved === segmentInfo.resolved
|
||||
)
|
||||
})
|
||||
) {
|
||||
// If the previous path is potentially resolved and
|
||||
// the next paths all point to the same resolved node,
|
||||
// then the next path is potentially resolved.
|
||||
potential = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (potential) {
|
||||
return {
|
||||
resolved: prevSegmentInfo.potentiallyResolved,
|
||||
kind: 'potential',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sameRoute = findSameRoutePathSegment(segment)
|
||||
if (sameRoute) {
|
||||
const sameRouteSegmentInfo = this._getProcessedSegmentInfo(sameRoute)
|
||||
if (sameRouteSegmentInfo.potentiallyResolved) {
|
||||
return {
|
||||
resolved: sameRouteSegmentInfo.potentiallyResolved,
|
||||
kind: 'potential',
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
/**
|
||||
* @param {CodePathSegment} segment
|
||||
* @param {PromiseCodePathContext} promiseCodePathContext
|
||||
*/
|
||||
_getProcessedSegmentInfo(segment, promiseCodePathContext) {
|
||||
const segmentInfo = this.segmentInfos.get(segment)
|
||||
if (segmentInfo) {
|
||||
return segmentInfo
|
||||
}
|
||||
const newInfo = new CodePathSegmentInfo(this, segment)
|
||||
this.segmentInfos.set(segment, newInfo)
|
||||
|
||||
const alreadyResolvedData = this._getAlreadyResolvedData(
|
||||
segment,
|
||||
promiseCodePathContext
|
||||
)
|
||||
if (alreadyResolvedData) {
|
||||
if (alreadyResolvedData.kind === 'certain') {
|
||||
newInfo.resolved = alreadyResolvedData.resolved
|
||||
} else {
|
||||
newInfo.potentiallyResolved = alreadyResolvedData.resolved
|
||||
}
|
||||
}
|
||||
return newInfo
|
||||
}
|
||||
}
|
||||
|
||||
class CodePathSegmentInfo {
|
||||
/**
|
||||
* @param {CodePathInfo} pathInfo
|
||||
* @param {CodePathSegment} segment
|
||||
*/
|
||||
constructor(pathInfo, segment) {
|
||||
this.pathInfo = pathInfo
|
||||
this.segment = segment
|
||||
/** @type {Identifier | null} */
|
||||
this._resolved = null
|
||||
/** @type {Identifier | null} */
|
||||
this.potentiallyResolved = null
|
||||
}
|
||||
|
||||
get resolved() {
|
||||
return this._resolved
|
||||
}
|
||||
/** @type {Identifier} */
|
||||
set resolved(identifier) {
|
||||
this._resolved = identifier
|
||||
this.pathInfo.resolvedCount++
|
||||
}
|
||||
}
|
||||
|
||||
class PromiseCodePathContext {
|
||||
constructor() {
|
||||
/** @type {Set<string>} */
|
||||
this.resolvedSegmentIds = new Set()
|
||||
}
|
||||
/** @param {CodePathSegment} */
|
||||
addResolvedTryBlockCodePathSegment(segment) {
|
||||
this.resolvedSegmentIds.add(segment.id)
|
||||
}
|
||||
/** @param {CodePathSegment} */
|
||||
isResolvedTryBlockCodePathSegment(segment) {
|
||||
return this.resolvedSegmentIds.has(segment.id)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description:
|
||||
'Disallow creating new promises with paths that resolve multiple times.',
|
||||
url: getDocsUrl('no-multiple-resolved'),
|
||||
},
|
||||
messages: {
|
||||
alreadyResolved:
|
||||
'Promise should not be resolved multiple times. Promise is already resolved on line {{line}}.',
|
||||
potentiallyAlreadyResolved:
|
||||
'Promise should not be resolved multiple times. Promise is potentially resolved on line {{line}}.',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
/** @param {import('eslint').Rule.RuleContext} context */
|
||||
create(context) {
|
||||
const reported = new Set()
|
||||
const promiseCodePathContext = new PromiseCodePathContext()
|
||||
/**
|
||||
* @param {Identifier} node
|
||||
* @param {Identifier} resolved
|
||||
* @param {'certain' | 'potential'} kind
|
||||
*/
|
||||
function report(node, resolved, kind) {
|
||||
if (reported.has(node)) {
|
||||
return
|
||||
}
|
||||
reported.add(node)
|
||||
context.report({
|
||||
node: node.parent,
|
||||
messageId:
|
||||
kind === 'certain' ? 'alreadyResolved' : 'potentiallyAlreadyResolved',
|
||||
data: {
|
||||
line: resolved.loc.start.line,
|
||||
},
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @param {CodePathInfo} codePathInfo
|
||||
* @param {PromiseCodePathContext} promiseCodePathContext
|
||||
*/
|
||||
function verifyMultipleResolvedPath(codePathInfo, promiseCodePathContext) {
|
||||
for (const { node, resolved, kind } of codePathInfo.iterateReports(
|
||||
promiseCodePathContext
|
||||
)) {
|
||||
report(node, resolved, kind)
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {CodePathInfo[]} */
|
||||
const codePathInfoStack = []
|
||||
/** @type {Set<Identifier>[]} */
|
||||
const resolverReferencesStack = [new Set()]
|
||||
/** @type {ThrowableExpression | null} */
|
||||
let lastThrowableExpression = null
|
||||
return {
|
||||
/** @param {FunctionExpression | ArrowFunctionExpression} node */
|
||||
'FunctionExpression, ArrowFunctionExpression'(node) {
|
||||
if (!isPromiseConstructorWithInlineExecutor(node.parent)) {
|
||||
return
|
||||
}
|
||||
// Collect and stack `resolve` and `reject` references.
|
||||
/** @type {Set<Identifier>} */
|
||||
const resolverReferences = new Set()
|
||||
const resolvers = node.params.filter(
|
||||
/** @returns {node is Identifier} */
|
||||
(node) => node && node.type === 'Identifier'
|
||||
)
|
||||
for (const resolver of resolvers) {
|
||||
const variable = getScope(context, node).set.get(resolver.name)
|
||||
// istanbul ignore next -- Usually always present.
|
||||
if (!variable) continue
|
||||
for (const reference of variable.references) {
|
||||
resolverReferences.add(reference.identifier)
|
||||
}
|
||||
}
|
||||
|
||||
resolverReferencesStack.unshift(resolverReferences)
|
||||
},
|
||||
/** @param {FunctionExpression | ArrowFunctionExpression} node */
|
||||
'FunctionExpression, ArrowFunctionExpression:exit'(node) {
|
||||
if (!isPromiseConstructorWithInlineExecutor(node.parent)) {
|
||||
return
|
||||
}
|
||||
resolverReferencesStack.shift()
|
||||
},
|
||||
/** @param {CodePath} path */
|
||||
onCodePathStart(path) {
|
||||
codePathInfoStack.unshift(new CodePathInfo(path))
|
||||
},
|
||||
onCodePathEnd() {
|
||||
const codePathInfo = codePathInfoStack.shift()
|
||||
if (codePathInfo.resolvedCount > 1) {
|
||||
verifyMultipleResolvedPath(codePathInfo, promiseCodePathContext)
|
||||
}
|
||||
},
|
||||
/** @param {ThrowableExpression} node */
|
||||
'CallExpression, MemberExpression, NewExpression, ImportExpression, YieldExpression:exit'(
|
||||
node
|
||||
) {
|
||||
lastThrowableExpression = node
|
||||
},
|
||||
/**
|
||||
* @param {CodePathSegment} segment
|
||||
* @param {Node} node
|
||||
*/
|
||||
onCodePathSegmentEnd(segment, node) {
|
||||
if (
|
||||
node.type === 'CatchClause' &&
|
||||
lastThrowableExpression &&
|
||||
lastThrowableExpression.type === 'CallExpression' &&
|
||||
node.parent.type === 'TryStatement' &&
|
||||
node.parent.range[0] <= lastThrowableExpression.range[0] &&
|
||||
lastThrowableExpression.range[1] <= node.parent.range[1]
|
||||
) {
|
||||
const resolverReferences = resolverReferencesStack[0]
|
||||
if (resolverReferences.has(lastThrowableExpression.callee)) {
|
||||
// Mark a segment if the last expression in the try block is a call to resolve.
|
||||
promiseCodePathContext.addResolvedTryBlockCodePathSegment(segment)
|
||||
}
|
||||
}
|
||||
},
|
||||
/** @type {Identifier} */
|
||||
'CallExpression > Identifier.callee'(node) {
|
||||
const codePathInfo = codePathInfoStack[0]
|
||||
const resolverReferences = resolverReferencesStack[0]
|
||||
if (!resolverReferences.has(node)) {
|
||||
return
|
||||
}
|
||||
for (const segmentInfo of codePathInfo.getCurrentSegmentInfos()) {
|
||||
// If a resolving path is found, report if the path is already resolved.
|
||||
// Store the information if it is not already resolved.
|
||||
if (segmentInfo.resolved) {
|
||||
report(node, segmentInfo.resolved, 'certain')
|
||||
continue
|
||||
}
|
||||
segmentInfo.resolved = node
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue