From e09ac592f65df976466cf832e31bb37cc88ee320 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Tue, 3 Mar 2020 11:23:00 -0800 Subject: [PATCH 1/2] feat: expose utils via utilities package --- .cspell.json | 3 +- packages/eslint-plugin/package.json | 1 - .../eslint-plugin/src/rules/comma-spacing.ts | 8 +- .../src/rules/func-call-spacing.ts | 5 +- .../src/rules/indent-new-do-not-use/index.ts | 13 +- .../src/rules/no-array-constructor.ts | 2 +- .../src/rules/prefer-includes.ts | 3 +- .../src/rules/prefer-optional-chain.ts | 3 +- .../src/rules/prefer-regexp-exec.ts | 8 +- .../rules/prefer-string-starts-ends-with.ts | 8 +- .../eslint-plugin/src/rules/require-await.ts | 13 +- .../src/rules/space-before-function-paren.ts | 5 +- .../src/rules/switch-exhaustiveness-check.ts | 5 +- packages/eslint-plugin/src/util/astUtils.ts | 267 +----------------- packages/eslint-plugin/src/util/misc.ts | 5 +- .../eslint-plugin/typings/eslint-utils.d.ts | 180 ------------ packages/experimental-utils/package.json | 3 +- .../ast-utils/eslint-utils/PatternMatcher.ts | 56 ++++ .../eslint-utils/ReferenceTracker.ts | 94 ++++++ .../ast-utils/eslint-utils/astUtilities.ts | 127 +++++++++ .../src/ast-utils/eslint-utils/index.ts | 5 + .../src/ast-utils/eslint-utils/predicates.ts | 106 +++++++ .../ast-utils/eslint-utils/scopeAnalysis.ts | 27 ++ .../experimental-utils/src/ast-utils/index.ts | 3 + .../experimental-utils/src/ast-utils/misc.ts | 15 + .../src/ast-utils/predicates.ts | 247 ++++++++++++++++ .../src/eslint-utils/RuleCreator.ts | 4 +- .../src/eslint-utils/applyDefault.ts | 4 +- .../src/eslint-utils/deepMerge.ts | 4 +- .../src/eslint-utils/getParserServices.ts | 7 +- packages/experimental-utils/src/index.ts | 17 +- .../src/ts-eslint-scope/Definition.ts | 2 +- .../src/ts-eslint-scope/Options.ts | 2 +- .../src/ts-eslint-scope/PatternVisitor.ts | 2 +- .../src/ts-eslint-scope/Reference.ts | 2 +- .../src/ts-eslint-scope/Referencer.ts | 2 +- .../src/ts-eslint-scope/Scope.ts | 2 +- .../src/ts-eslint-scope/ScopeManager.ts | 2 +- .../src/ts-eslint-scope/Variable.ts | 2 +- .../experimental-utils/src/ts-eslint/AST.ts | 5 +- .../src/ts-eslint/Linter.ts | 2 +- .../src/ts-eslint/ParserOptions.ts | 4 +- .../experimental-utils/src/ts-eslint/Rule.ts | 2 +- .../src/ts-eslint/RuleTester.ts | 5 +- .../experimental-utils/src/ts-eslint/Scope.ts | 2 +- .../src/ts-eslint/SourceCode.ts | 4 +- packages/experimental-utils/src/ts-estree.ts | 12 + .../typings/eslint-utils.d.ts | 40 +++ yarn.lock | 7 + 49 files changed, 821 insertions(+), 526 deletions(-) delete mode 100644 packages/eslint-plugin/typings/eslint-utils.d.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/PatternMatcher.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/ReferenceTracker.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/astUtilities.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/index.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/predicates.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/scopeAnalysis.ts create mode 100644 packages/experimental-utils/src/ast-utils/index.ts create mode 100644 packages/experimental-utils/src/ast-utils/misc.ts create mode 100644 packages/experimental-utils/src/ast-utils/predicates.ts create mode 100644 packages/experimental-utils/src/ts-estree.ts create mode 100644 packages/experimental-utils/typings/eslint-utils.d.ts diff --git a/.cspell.json b/.cspell.json index 33f7b70c5f2..c65ff294c24 100644 --- a/.cspell.json +++ b/.cspell.json @@ -10,7 +10,8 @@ "**/**/CONTRIBUTORS.md", "**/**/ROADMAP.md", "**/*.{json,snap}", - ".cspell.json" + ".cspell.json", + "yarn.lock" ], "dictionaries": [ "typescript", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index acee148c5f9..ae794c86d87 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -42,7 +42,6 @@ }, "dependencies": { "@typescript-eslint/experimental-utils": "2.22.0", - "eslint-utils": "^1.4.3", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "tsutils": "^3.17.1" diff --git a/packages/eslint-plugin/src/rules/comma-spacing.ts b/packages/eslint-plugin/src/rules/comma-spacing.ts index d8ab4b42b8d..665eeffd029 100644 --- a/packages/eslint-plugin/src/rules/comma-spacing.ts +++ b/packages/eslint-plugin/src/rules/comma-spacing.ts @@ -2,8 +2,12 @@ import { TSESTree, AST_TOKEN_TYPES, } from '@typescript-eslint/experimental-utils'; -import { isClosingParenToken, isCommaToken } from 'eslint-utils'; -import { isTokenOnSameLine, createRule } from '../util'; +import { + isClosingParenToken, + isCommaToken, + isTokenOnSameLine, + createRule, +} from '../util'; type Options = [ { diff --git a/packages/eslint-plugin/src/rules/func-call-spacing.ts b/packages/eslint-plugin/src/rules/func-call-spacing.ts index 8e11d58592b..53858ad18ab 100644 --- a/packages/eslint-plugin/src/rules/func-call-spacing.ts +++ b/packages/eslint-plugin/src/rules/func-call-spacing.ts @@ -1,5 +1,4 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; -import { isOpeningParenToken } from 'eslint-utils'; import * as util from '../util'; export type Options = [ @@ -79,7 +78,7 @@ export default util.createRule({ | TSESTree.OptionalCallExpression | TSESTree.NewExpression, ): void { - const isOptionalCall = util.isOptionalOptionalChain(node); + const isOptionalCall = util.isOptionalOptionalCallExpression(node); const closingParenToken = sourceCode.getLastToken(node)!; const lastCalleeTokenWithoutPossibleParens = sourceCode.getLastToken( @@ -88,7 +87,7 @@ export default util.createRule({ const openingParenToken = sourceCode.getFirstTokenBetween( lastCalleeTokenWithoutPossibleParens, closingParenToken, - isOpeningParenToken, + util.isOpeningParenToken, ); if (!openingParenToken || openingParenToken.range[1] >= node.range[1]) { // new expression with no parens... diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts index 923423dbc14..3c24f29ad9e 100644 --- a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts @@ -8,7 +8,13 @@ import { TSESLint, TSESTree, } from '@typescript-eslint/experimental-utils'; + +import { TokenOrComment } from './BinarySearchTree'; +import { OffsetStorage } from './OffsetStorage'; +import { TokenInfo } from './TokenInfo'; import { + createRule, + ExcludeKeys, isClosingBraceToken, isClosingBracketToken, isClosingParenToken, @@ -19,11 +25,8 @@ import { isOpeningBraceToken, isOpeningParenToken, isSemicolonToken, -} from 'eslint-utils'; -import { TokenOrComment } from './BinarySearchTree'; -import { OffsetStorage } from './OffsetStorage'; -import { TokenInfo } from './TokenInfo'; -import { createRule, ExcludeKeys, RequireKeys } from '../../util'; + RequireKeys, +} from '../../util'; const GLOBAL_LINEBREAK_REGEX = /\r\n|[\r\n\u2028\u2029]/gu; const WHITESPACE_REGEX = /\s*$/u; diff --git a/packages/eslint-plugin/src/rules/no-array-constructor.ts b/packages/eslint-plugin/src/rules/no-array-constructor.ts index dddae97f663..c9465e70799 100644 --- a/packages/eslint-plugin/src/rules/no-array-constructor.ts +++ b/packages/eslint-plugin/src/rules/no-array-constructor.ts @@ -37,7 +37,7 @@ export default util.createRule({ node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === 'Array' && !node.typeParameters && - !util.isOptionalOptionalChain(node) + !util.isOptionalOptionalCallExpression(node) ) { context.report({ node, diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 0cd54d6c786..b31280b4d46 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -2,10 +2,9 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { getStaticValue } from 'eslint-utils'; import { AST as RegExpAST, parseRegExpLiteral } from 'regexpp'; import * as ts from 'typescript'; -import { createRule, getParserServices } from '../util'; +import { createRule, getParserServices, getStaticValue } from '../util'; export default createRule({ name: 'prefer-includes', diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index e0740c2e913..0ec33df8180 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -2,7 +2,6 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { isOpeningParenToken } from 'eslint-utils'; import * as util from '../util'; type ValidChainTarget = @@ -203,7 +202,7 @@ export default util.createRule({ sourceCode.getFirstTokenBetween( node.callee, closingParenToken, - isOpeningParenToken, + util.isOpeningParenToken, ), util.NullThrowsReasons.MissingToken('opening parenthesis', node.type), ); diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index 599e0f2a6da..dda6f15d3a5 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -1,6 +1,10 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; -import { getStaticValue } from 'eslint-utils'; -import { createRule, getParserServices, getTypeName } from '../util'; +import { + createRule, + getParserServices, + getStaticValue, + getTypeName, +} from '../util'; export default createRule({ name: 'prefer-regexp-exec', diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index 2b4946b2a14..217eada2e85 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -3,13 +3,15 @@ import { TSESLint, TSESTree, } from '@typescript-eslint/experimental-utils'; +import { AST as RegExpAST, RegExpParser } from 'regexpp'; import { + createRule, + getParserServices, getPropertyName, getStaticValue, + getTypeName, isNotClosingParenToken, -} from 'eslint-utils'; -import { AST as RegExpAST, RegExpParser } from 'regexpp'; -import { createRule, getParserServices, getTypeName } from '../util'; +} from '../util'; const EQ_OPERATORS = /^[=!]=/; const regexpp = new RegExpParser(); diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts index 4688373dc38..e7b6a5f5959 100644 --- a/packages/eslint-plugin/src/rules/require-await.ts +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -3,11 +3,6 @@ import { TSESLint, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { - isArrowToken, - getFunctionNameWithKind, - isOpeningParenToken, -} from 'eslint-utils'; import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -73,7 +68,7 @@ export default util.createRule({ loc: getFunctionHeadLoc(node, sourceCode), messageId: 'missingAwait', data: { - name: util.upperCaseFirst(getFunctionNameWithKind(node)), + name: util.upperCaseFirst(util.getFunctionNameWithKind(node)), }, }); } @@ -157,8 +152,8 @@ function getOpeningParenOfParams( ): TSESTree.Token { return util.nullThrows( node.id - ? sourceCode.getTokenAfter(node.id, isOpeningParenToken) - : sourceCode.getFirstToken(node, isOpeningParenToken), + ? sourceCode.getTokenAfter(node.id, util.isOpeningParenToken) + : sourceCode.getFirstToken(node, util.isOpeningParenToken), util.NullThrowsReasons.MissingToken('(', node.type), ); } @@ -180,7 +175,7 @@ function getFunctionHeadLoc( if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) { const arrowToken = util.nullThrows( - sourceCode.getTokenBefore(node.body, isArrowToken), + sourceCode.getTokenBefore(node.body, util.isArrowToken), util.NullThrowsReasons.MissingToken('=>', node.type), ); diff --git a/packages/eslint-plugin/src/rules/space-before-function-paren.ts b/packages/eslint-plugin/src/rules/space-before-function-paren.ts index e4b4cb2f960..bc2ad8b0d04 100644 --- a/packages/eslint-plugin/src/rules/space-before-function-paren.ts +++ b/packages/eslint-plugin/src/rules/space-before-function-paren.ts @@ -2,7 +2,6 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { isOpeningParenToken } from 'eslint-utils'; import * as util from '../util'; type Option = 'never' | 'always'; @@ -106,7 +105,7 @@ export default util.createRule({ // Always ignore non-async functions and arrow functions without parens, e.g. async foo => bar if ( node.async && - isOpeningParenToken(sourceCode.getFirstToken(node, { skip: 1 })!) + util.isOpeningParenToken(sourceCode.getFirstToken(node, { skip: 1 })!) ) { return overrideConfig.asyncArrow ?? baseConfig; } @@ -143,7 +142,7 @@ export default util.createRule({ leftToken = sourceCode.getLastToken(node.typeParameters)!; rightToken = sourceCode.getTokenAfter(leftToken)!; } else { - rightToken = sourceCode.getFirstToken(node, isOpeningParenToken)!; + rightToken = sourceCode.getFirstToken(node, util.isOpeningParenToken)!; leftToken = sourceCode.getTokenBefore(rightToken)!; } const hasSpacing = sourceCode.isSpaceBetweenTokens(leftToken, rightToken); diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index 2cf394d2026..414dbbcdd03 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -2,11 +2,12 @@ import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; import * as ts from 'typescript'; import { createRule, - getParserServices, getConstrainedTypeAtLocation, + getParserServices, + isClosingBraceToken, + isOpeningBraceToken, } from '../util'; import { isTypeFlagSet, unionTypeParts } from 'tsutils'; -import { isClosingBraceToken, isOpeningBraceToken } from 'eslint-utils'; export default createRule({ name: 'switch-exhaustiveness-check', diff --git a/packages/eslint-plugin/src/util/astUtils.ts b/packages/eslint-plugin/src/util/astUtils.ts index 03f917e4ffb..12f003e98d0 100644 --- a/packages/eslint-plugin/src/util/astUtils.ts +++ b/packages/eslint-plugin/src/util/astUtils.ts @@ -1,265 +1,2 @@ -import { - AST_NODE_TYPES, - AST_TOKEN_TYPES, - TSESTree, -} from '@typescript-eslint/experimental-utils'; - -const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/; - -function isOptionalChainPunctuator( - token: TSESTree.Token | TSESTree.Comment, -): token is TSESTree.PunctuatorToken & { value: '?.' } { - return token.type === AST_TOKEN_TYPES.Punctuator && token.value === '?.'; -} -function isNotOptionalChainPunctuator( - token: TSESTree.Token | TSESTree.Comment, -): boolean { - return !isOptionalChainPunctuator(token); -} - -function isNonNullAssertionPunctuator( - token: TSESTree.Token | TSESTree.Comment, -): token is TSESTree.PunctuatorToken & { value: '!' } { - return token.type === AST_TOKEN_TYPES.Punctuator && token.value === '!'; -} -function isNotNonNullAssertionPunctuator( - token: TSESTree.Token | TSESTree.Comment, -): boolean { - return !isNonNullAssertionPunctuator(token); -} - -/** - * Returns true if and only if the node represents: foo?.() or foo.bar?.() - */ -function isOptionalOptionalChain( - node: TSESTree.Node, -): node is TSESTree.OptionalCallExpression & { optional: true } { - return ( - node.type === AST_NODE_TYPES.OptionalCallExpression && - // this flag means the call expression itself is option - // i.e. it is foo.bar?.() and not foo?.bar() - node.optional - ); -} - -/** - * Returns true if and only if the node represents logical OR - */ -function isLogicalOrOperator( - node: TSESTree.Node, -): node is TSESTree.LogicalExpression & { operator: '||' } { - return ( - node.type === AST_NODE_TYPES.LogicalExpression && node.operator === '||' - ); -} - -/** - * Determines whether two adjacent tokens are on the same line - */ -function isTokenOnSameLine( - left: TSESTree.Token | TSESTree.Comment, - right: TSESTree.Token | TSESTree.Comment, -): boolean { - return left.loc.end.line === right.loc.start.line; -} - -/** - * Checks if a node is a type assertion: - * ``` - * x as foo - * x - * ``` - */ -function isTypeAssertion( - node: TSESTree.Node | undefined | null, -): node is TSESTree.TSAsExpression | TSESTree.TSTypeAssertion { - if (!node) { - return false; - } - return ( - node.type === AST_NODE_TYPES.TSAsExpression || - node.type === AST_NODE_TYPES.TSTypeAssertion - ); -} - -function isVariableDeclarator( - node: TSESTree.Node | undefined, -): node is TSESTree.VariableDeclarator { - return node?.type === AST_NODE_TYPES.VariableDeclarator; -} - -function isFunction( - node: TSESTree.Node | undefined, -): node is - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression { - if (!node) { - return false; - } - - return [ - AST_NODE_TYPES.ArrowFunctionExpression, - AST_NODE_TYPES.FunctionDeclaration, - AST_NODE_TYPES.FunctionExpression, - ].includes(node.type); -} - -function isFunctionType( - node: TSESTree.Node | undefined, -): node is - | TSESTree.TSCallSignatureDeclaration - | TSESTree.TSConstructorType - | TSESTree.TSConstructSignatureDeclaration - | TSESTree.TSEmptyBodyFunctionExpression - | TSESTree.TSFunctionType - | TSESTree.TSMethodSignature { - if (!node) { - return false; - } - - return [ - AST_NODE_TYPES.TSCallSignatureDeclaration, - AST_NODE_TYPES.TSConstructorType, - AST_NODE_TYPES.TSConstructSignatureDeclaration, - AST_NODE_TYPES.TSEmptyBodyFunctionExpression, - AST_NODE_TYPES.TSFunctionType, - AST_NODE_TYPES.TSMethodSignature, - ].includes(node.type); -} - -function isFunctionOrFunctionType( - node: TSESTree.Node | undefined, -): node is - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.TSCallSignatureDeclaration - | TSESTree.TSConstructorType - | TSESTree.TSConstructSignatureDeclaration - | TSESTree.TSEmptyBodyFunctionExpression - | TSESTree.TSFunctionType - | TSESTree.TSMethodSignature { - return isFunction(node) || isFunctionType(node); -} - -function isTSFunctionType( - node: TSESTree.Node | undefined, -): node is TSESTree.TSFunctionType { - return node?.type === AST_NODE_TYPES.TSFunctionType; -} - -function isTSConstructorType( - node: TSESTree.Node | undefined, -): node is TSESTree.TSConstructorType { - return node?.type === AST_NODE_TYPES.TSConstructorType; -} - -function isClassOrTypeElement( - node: TSESTree.Node | undefined, -): node is TSESTree.ClassElement | TSESTree.TypeElement { - if (!node) { - return false; - } - - return [ - // ClassElement - AST_NODE_TYPES.ClassProperty, - AST_NODE_TYPES.FunctionExpression, - AST_NODE_TYPES.MethodDefinition, - AST_NODE_TYPES.TSAbstractClassProperty, - AST_NODE_TYPES.TSAbstractMethodDefinition, - AST_NODE_TYPES.TSEmptyBodyFunctionExpression, - AST_NODE_TYPES.TSIndexSignature, - // TypeElement - AST_NODE_TYPES.TSCallSignatureDeclaration, - AST_NODE_TYPES.TSConstructSignatureDeclaration, - // AST_NODE_TYPES.TSIndexSignature, - AST_NODE_TYPES.TSMethodSignature, - AST_NODE_TYPES.TSPropertySignature, - ].includes(node.type); -} - -/** - * Checks if a node is a constructor method. - */ -function isConstructor( - node: TSESTree.Node | undefined, -): node is TSESTree.MethodDefinition { - return ( - node?.type === AST_NODE_TYPES.MethodDefinition && - node.kind === 'constructor' - ); -} - -/** - * Checks if a node is a setter method. - */ -function isSetter( - node: TSESTree.Node | undefined, -): node is TSESTree.MethodDefinition | TSESTree.Property { - return ( - !!node && - (node.type === AST_NODE_TYPES.MethodDefinition || - node.type === AST_NODE_TYPES.Property) && - node.kind === 'set' - ); -} - -function isIdentifier( - node: TSESTree.Node | undefined, -): node is TSESTree.Identifier { - return node?.type === AST_NODE_TYPES.Identifier; -} - -/** - * Checks if a node represents an `await …` expression. - */ -function isAwaitExpression( - node: TSESTree.Node | undefined | null, -): node is TSESTree.AwaitExpression { - return node?.type === AST_NODE_TYPES.AwaitExpression; -} - -/** - * Checks if a possible token is the `await` keyword. - */ -function isAwaitKeyword( - node: TSESTree.Token | TSESTree.Comment | undefined | null, -): node is TSESTree.KeywordToken & { value: 'await' } { - return node?.type === AST_TOKEN_TYPES.Identifier && node.value === 'await'; -} - -function isMemberOrOptionalMemberExpression( - node: TSESTree.Node, -): node is TSESTree.MemberExpression | TSESTree.OptionalMemberExpression { - return ( - node.type === AST_NODE_TYPES.MemberExpression || - node.type === AST_NODE_TYPES.OptionalMemberExpression - ); -} - -export { - isAwaitExpression, - isAwaitKeyword, - isConstructor, - isClassOrTypeElement, - isFunction, - isFunctionOrFunctionType, - isFunctionType, - isIdentifier, - isLogicalOrOperator, - isMemberOrOptionalMemberExpression, - isNonNullAssertionPunctuator, - isNotNonNullAssertionPunctuator, - isNotOptionalChainPunctuator, - isOptionalChainPunctuator, - isOptionalOptionalChain, - isSetter, - isTokenOnSameLine, - isTSConstructorType, - isTSFunctionType, - isTypeAssertion, - isVariableDeclarator, - LINEBREAK_MATCHER, -}; +// deeply re-export, for convenience +export * from '@typescript-eslint/experimental-utils/dist/ast-utils'; diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index c024b6a845c..e9f50fd2107 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -82,9 +82,7 @@ function findFirstResult( /** * Gets a string representation of the name of the index signature. */ -export function getNameFromIndexSignature( - node: TSESTree.TSIndexSignature, -): string { +function getNameFromIndexSignature(node: TSESTree.TSIndexSignature): string { const propName: TSESTree.PropertyName | undefined = node.parameters.find( (parameter: TSESTree.Parameter): parameter is TSESTree.Identifier => parameter.type === AST_NODE_TYPES.Identifier, @@ -136,6 +134,7 @@ export { ExcludeKeys, findFirstResult, getEnumNames, + getNameFromIndexSignature, getNameFromMember, InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, diff --git a/packages/eslint-plugin/typings/eslint-utils.d.ts b/packages/eslint-plugin/typings/eslint-utils.d.ts deleted file mode 100644 index f2fc090c7a6..00000000000 --- a/packages/eslint-plugin/typings/eslint-utils.d.ts +++ /dev/null @@ -1,180 +0,0 @@ -declare module 'eslint-utils' { - import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - - export function getFunctionHeadLocation( - node: - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.ArrowFunctionExpression, - sourceCode: TSESLint.SourceCode, - ): TSESTree.SourceLocation; - - export function getFunctionNameWithKind( - node: - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.ArrowFunctionExpression, - ): string; - - export function getPropertyName( - node: - | TSESTree.MemberExpression - | TSESTree.OptionalMemberExpression - | TSESTree.Property - | TSESTree.MethodDefinition, - initialScope?: TSESLint.Scope.Scope, - ): string | null; - - export function getStaticValue( - node: TSESTree.Node, - initialScope?: TSESLint.Scope.Scope, - ): { value: unknown } | null; - - export function getStringIfConstant( - node: TSESTree.Node, - initialScope?: TSESLint.Scope.Scope, - ): string | null; - - export function hasSideEffect( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, - options?: { - considerGetters?: boolean; - considerImplicitTypeConversion?: boolean; - }, - ): boolean; - - export function isParenthesized( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, - ): boolean; - - export class PatternMatcher { - constructor(pattern: RegExp, options?: { escaped?: boolean }); - execAll(str: string): IterableIterator; - test(str: string): boolean; - } - - export function findVariable( - initialScope: TSESLint.Scope.Scope, - name: string, - ): TSESLint.Scope.Variable | null; - - export function getInnermostScope( - initialScope: TSESLint.Scope.Scope, - node: TSESTree.Node, - ): TSESLint.Scope.Scope; - - export class ReferenceTracker { - static readonly READ: unique symbol; - static readonly CALL: unique symbol; - static readonly CONSTRUCT: unique symbol; - - constructor( - globalScope: TSESLint.Scope.Scope, - options?: { - mode: 'strict' | 'legacy'; - globalObjectNames: readonly string[]; - }, - ); - - iterateGlobalReferences( - traceMap: ReferenceTracker.TraceMap, - ): IterableIterator>; - iterateCjsReferences( - traceMap: ReferenceTracker.TraceMap, - ): IterableIterator>; - iterateEsmReferences( - traceMap: ReferenceTracker.TraceMap, - ): IterableIterator>; - } - - export namespace ReferenceTracker { - export type READ = typeof ReferenceTracker.READ; - export type CALL = typeof ReferenceTracker.READ; - export type CONSTRUCT = typeof ReferenceTracker.READ; - export type ReferenceType = READ | CALL | CONSTRUCT; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - export type TraceMap = Record>; - export interface TraceMapElement { - [ReferenceTracker.READ]?: T; - [ReferenceTracker.CALL]?: T; - [ReferenceTracker.CONSTRUCT]?: T; - [key: string]: TraceMapElement; - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - export interface FoundReference { - node: TSESTree.Node; - path: readonly string[]; - type: ReferenceType; - entry: T; - } - } - - export function isArrowToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: '=>' }; - export function isNotArrowToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isClosingBraceToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: '}' }; - export function isNotClosingBraceToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isClosingBracketToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: ']' }; - export function isNotClosingBracketToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isClosingParenToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: ')' }; - export function isNotClosingParenToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isColonToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: ':' }; - export function isNotColonToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isCommaToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: ',' }; - export function isNotCommaToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isCommentToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.Comment; - export function isNotCommentToken< - T extends TSESTree.Token | TSESTree.Comment - >(token: T): token is Exclude; - export function isOpeningBraceToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: '{' }; - export function isNotOpeningBraceToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isOpeningBracketToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: '[' }; - export function isNotOpeningBracketToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isOpeningParenToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: '(' }; - export function isNotOpeningParenToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isSemicolonToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: ';' }; - export function isNotSemicolonToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; -} diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 72b9c0436b3..5c347176bd5 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -38,7 +38,8 @@ "dependencies": { "@types/json-schema": "^7.0.3", "@typescript-eslint/typescript-estree": "2.22.0", - "eslint-scope": "^5.0.0" + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" }, "peerDependencies": { "eslint": "*" diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/PatternMatcher.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/PatternMatcher.ts new file mode 100644 index 00000000000..3f45db7db46 --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/PatternMatcher.ts @@ -0,0 +1,56 @@ +import * as eslintUtils from 'eslint-utils'; + +interface PatternMatcher { + /** + * Iterate all matched parts in a given string. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#matcher-execall} + */ + execAll(str: string): IterableIterator; + + /** + * Check whether this pattern matches a given string or not. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#matcher-test} + */ + test(str: string): boolean; + + /** + * Replace all matched parts by a given replacer. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#matcher-symbol-replace} + * @example + * 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]" + * ) + * }, + * } + * }, + * } + */ + [Symbol.replace]( + str: string, + replacer: string | ((...strs: string[]) => string), + ): string; +} + +/** + * The class to find a pattern in strings as handling escape sequences. + * It ignores the found pattern if it's escaped with `\`. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#patternmatcher-class} + */ +const PatternMatcher = eslintUtils.PatternMatcher as { + new (pattern: RegExp, options?: { escaped?: boolean }): PatternMatcher; +}; + +export { PatternMatcher }; diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/ReferenceTracker.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/ReferenceTracker.ts new file mode 100644 index 00000000000..2298ac1fb1b --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/ReferenceTracker.ts @@ -0,0 +1,94 @@ +/* eslint-disable @typescript-eslint/no-namespace */ + +import * as eslintUtils from 'eslint-utils'; +import { TSESTree } from '../../ts-estree'; +import * as TSESLint from '../../ts-eslint'; + +const ReferenceTrackerREAD: unique symbol = eslintUtils.ReferenceTracker.READ; +const ReferenceTrackerCALL: unique symbol = eslintUtils.ReferenceTracker.CALL; +const ReferenceTrackerCONSTRUCT: unique symbol = + eslintUtils.ReferenceTracker.CONSTRUCT; + +interface ReferenceTracker { + /** + * Iterate the references that the given `traceMap` determined. + * This method starts to search from global variables. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#tracker-iterateglobalreferences} + */ + iterateGlobalReferences( + traceMap: ReferenceTracker.TraceMap, + ): IterableIterator>; + + /** + * Iterate the references that the given `traceMap` determined. + * This method starts to search from `require()` expression. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#tracker-iteratecjsreferences} + */ + iterateCjsReferences( + traceMap: ReferenceTracker.TraceMap, + ): IterableIterator>; + + /** + * Iterate the references that the given `traceMap` determined. + * This method starts to search from `import`/`export` declarations. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#tracker-iterateesmreferences} + */ + iterateEsmReferences( + traceMap: ReferenceTracker.TraceMap, + ): IterableIterator>; +} +interface ReferenceTrackerStatic { + new ( + globalScope: TSESLint.Scope.Scope, + options?: { + /** + * 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"`. + */ + mode: 'strict' | 'legacy'; + /** + * The name list of Global Object. Optional. Default is `["global", "globalThis", "self", "window"]`. + */ + globalObjectNames: readonly string[]; + }, + ): ReferenceTracker; + + readonly READ: typeof ReferenceTrackerREAD; + readonly CALL: typeof ReferenceTrackerCALL; + readonly CONSTRUCT: typeof ReferenceTrackerCONSTRUCT; +} + +namespace ReferenceTracker { + export type READ = ReferenceTrackerStatic['READ']; + export type CALL = ReferenceTrackerStatic['CALL']; + export type CONSTRUCT = ReferenceTrackerStatic['CONSTRUCT']; + export type ReferenceType = READ | CALL | CONSTRUCT; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + export type TraceMap = Record>; + export interface TraceMapElement { + [ReferenceTrackerREAD]?: T; + [ReferenceTrackerCALL]?: T; + [ReferenceTrackerCONSTRUCT]?: T; + [key: string]: TraceMapElement; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + export interface FoundReference { + node: TSESTree.Node; + path: readonly string[]; + type: ReferenceType; + entry: T; + } +} + +/** + * The tracker for references. This provides reference tracking for global variables, CommonJS modules, and ES modules. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#referencetracker-class} + */ +const ReferenceTracker = eslintUtils.ReferenceTracker as ReferenceTrackerStatic; + +export { ReferenceTracker }; diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/astUtilities.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/astUtilities.ts new file mode 100644 index 00000000000..429b5a59cee --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/astUtilities.ts @@ -0,0 +1,127 @@ +import * as eslintUtils from 'eslint-utils'; +import { TSESTree } from '../../ts-estree'; +import * as TSESLint from '../../ts-eslint'; + +/** + * Get the proper location of a given function node to report. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#getfunctionheadlocation} + */ +const getFunctionHeadLocation = eslintUtils.getFunctionHeadLocation as ( + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression, + sourceCode: TSESLint.SourceCode, +) => TSESTree.SourceLocation; + +/** + * Get the name and kind of a given function node. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#getfunctionnamewithkind} + */ +const getFunctionNameWithKind = eslintUtils.getFunctionNameWithKind as ( + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression, +) => string; + +/** + * 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 function. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#getpropertyname} + * @returns The property name of the node. If the property name is not constant then it returns `null`. + */ +const getPropertyName = eslintUtils.getPropertyName as ( + node: + | TSESTree.MemberExpression + | TSESTree.OptionalMemberExpression + | TSESTree.Property + | TSESTree.MethodDefinition, + initialScope?: TSESLint.Scope.Scope, +) => string | null; + +/** + * 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. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#getstaticvalue} + * @returns 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`. + */ +const getStaticValue = eslintUtils.getStaticValue as ( + node: TSESTree.Node, + initialScope?: TSESLint.Scope.Scope, +) => { value: unknown } | null; + +/** + * Get the string value of a given node. + * This function is a tiny wrapper of the getStaticValue function. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#getstringifconstant} + */ +const getStringIfConstant = eslintUtils.getStringIfConstant as ( + node: TSESTree.Node, + initialScope?: TSESLint.Scope.Scope, +) => string | null; + +/** + * 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 = "-" | "+" | "!" | "~"])` + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#hassideeffect} + */ +const hasSideEffect = eslintUtils.hasSideEffect as ( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, + options?: { + considerGetters?: boolean; + considerImplicitTypeConversion?: boolean; + }, +) => boolean; + +/** + * Check whether a given node is parenthesized or not. + * This function detects it correctly even if it's parenthesized by specific syntax. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#isparenthesized} + * @returns `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)`. + */ +const isParenthesized = eslintUtils.isParenthesized as ( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +) => boolean; + +export { + getFunctionHeadLocation, + getFunctionNameWithKind, + getPropertyName, + getStaticValue, + getStringIfConstant, + hasSideEffect, + isParenthesized, +}; diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/index.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/index.ts new file mode 100644 index 00000000000..4a41363d1a3 --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/index.ts @@ -0,0 +1,5 @@ +export * from './astUtilities'; +export * from './PatternMatcher'; +export * from './predicates'; +export * from './ReferenceTracker'; +export * from './scopeAnalysis'; diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/predicates.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/predicates.ts new file mode 100644 index 00000000000..cbf8377127c --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/predicates.ts @@ -0,0 +1,106 @@ +import * as eslintUtils from 'eslint-utils'; +import { TSESTree } from '../../ts-estree'; + +const isArrowToken = eslintUtils.isArrowToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: '=>' }; +const isNotArrowToken = eslintUtils.isNotArrowToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isClosingBraceToken = eslintUtils.isClosingBraceToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: '}' }; +const isNotClosingBraceToken = eslintUtils.isNotClosingBraceToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isClosingBracketToken = eslintUtils.isClosingBracketToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: ']' }; +const isNotClosingBracketToken = eslintUtils.isNotClosingBracketToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isClosingParenToken = eslintUtils.isClosingParenToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: ')' }; +const isNotClosingParenToken = eslintUtils.isNotClosingParenToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isColonToken = eslintUtils.isColonToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: ':' }; +const isNotColonToken = eslintUtils.isNotColonToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isCommaToken = eslintUtils.isCommaToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: ',' }; +const isNotCommaToken = eslintUtils.isNotCommaToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isCommentToken = eslintUtils.isCommentToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.Comment; +const isNotCommentToken = eslintUtils.isNotCommentToken as < + T extends TSESTree.Token | TSESTree.Comment +>( + token: T, +) => token is Exclude; + +const isOpeningBraceToken = eslintUtils.isOpeningBraceToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: '{' }; +const isNotOpeningBraceToken = eslintUtils.isNotOpeningBraceToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isOpeningBracketToken = eslintUtils.isOpeningBracketToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: '[' }; +const isNotOpeningBracketToken = eslintUtils.isNotOpeningBracketToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isOpeningParenToken = eslintUtils.isOpeningParenToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: '(' }; +const isNotOpeningParenToken = eslintUtils.isNotOpeningParenToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isSemicolonToken = eslintUtils.isSemicolonToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: ';' }; +const isNotSemicolonToken = eslintUtils.isNotSemicolonToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +export { + isArrowToken, + isClosingBraceToken, + isClosingBracketToken, + isClosingParenToken, + isColonToken, + isCommaToken, + isCommentToken, + isNotArrowToken, + isNotClosingBraceToken, + isNotClosingBracketToken, + isNotClosingParenToken, + isNotColonToken, + isNotCommaToken, + isNotCommentToken, + isNotOpeningBraceToken, + isNotOpeningBracketToken, + isNotOpeningParenToken, + isNotSemicolonToken, + isOpeningBraceToken, + isOpeningBracketToken, + isOpeningParenToken, + isSemicolonToken, +}; diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/scopeAnalysis.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/scopeAnalysis.ts new file mode 100644 index 00000000000..15f9325a582 --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/scopeAnalysis.ts @@ -0,0 +1,27 @@ +import * as eslintUtils from 'eslint-utils'; +import { TSESTree } from '../../ts-estree'; +import * as TSESLint from '../../ts-eslint'; + +/** + * Get the variable of a given name. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#findvariable} + */ +const findVariable = eslintUtils.findVariable as ( + initialScope: TSESLint.Scope.Scope, + name: string, +) => TSESLint.Scope.Variable | null; + +/** + * Get the innermost scope which contains a given node. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#getinnermostscope} + * @returns The innermost scope which contains the given node. + * If such scope doesn't exist then it returns the 1st argument `initialScope`. + */ +const getInnermostScope = eslintUtils.getInnermostScope as ( + initialScope: TSESLint.Scope.Scope, + node: TSESTree.Node, +) => TSESLint.Scope.Scope; + +export { findVariable, getInnermostScope }; diff --git a/packages/experimental-utils/src/ast-utils/index.ts b/packages/experimental-utils/src/ast-utils/index.ts new file mode 100644 index 00000000000..c0ab325299a --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/index.ts @@ -0,0 +1,3 @@ +export * from './misc'; +export * from './predicates'; +export * from './eslint-utils'; diff --git a/packages/experimental-utils/src/ast-utils/misc.ts b/packages/experimental-utils/src/ast-utils/misc.ts new file mode 100644 index 00000000000..cf20f99a4cc --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/misc.ts @@ -0,0 +1,15 @@ +import { TSESTree } from '../ts-estree'; + +const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/; + +/** + * Determines whether two adjacent tokens are on the same line + */ +function isTokenOnSameLine( + left: TSESTree.Token | TSESTree.Comment, + right: TSESTree.Token | TSESTree.Comment, +): boolean { + return left.loc.end.line === right.loc.start.line; +} + +export { isTokenOnSameLine, LINEBREAK_MATCHER }; diff --git a/packages/experimental-utils/src/ast-utils/predicates.ts b/packages/experimental-utils/src/ast-utils/predicates.ts new file mode 100644 index 00000000000..b12f5d9ad6f --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/predicates.ts @@ -0,0 +1,247 @@ +import { AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree } from '../ts-estree'; + +function isOptionalChainPunctuator( + token: TSESTree.Token | TSESTree.Comment, +): token is TSESTree.PunctuatorToken & { value: '?.' } { + return token.type === AST_TOKEN_TYPES.Punctuator && token.value === '?.'; +} +function isNotOptionalChainPunctuator( + token: TSESTree.Token | TSESTree.Comment, +): boolean { + return !isOptionalChainPunctuator(token); +} + +function isNonNullAssertionPunctuator( + token: TSESTree.Token | TSESTree.Comment, +): token is TSESTree.PunctuatorToken & { value: '!' } { + return token.type === AST_TOKEN_TYPES.Punctuator && token.value === '!'; +} +function isNotNonNullAssertionPunctuator( + token: TSESTree.Token | TSESTree.Comment, +): boolean { + return !isNonNullAssertionPunctuator(token); +} + +/** + * Returns true if and only if the node represents: foo?.() or foo.bar?.() + */ +function isOptionalOptionalCallExpression( + node: TSESTree.Node, +): node is TSESTree.OptionalCallExpression & { optional: true } { + return ( + node.type === AST_NODE_TYPES.OptionalCallExpression && + // this flag means the call expression itself is option + // i.e. it is foo.bar?.() and not foo?.bar() + node.optional + ); +} + +/** + * Returns true if and only if the node represents logical OR + */ +function isLogicalOrOperator( + node: TSESTree.Node, +): node is TSESTree.LogicalExpression & { operator: '||' } { + return ( + node.type === AST_NODE_TYPES.LogicalExpression && node.operator === '||' + ); +} + +/** + * Checks if a node is a type assertion: + * ``` + * x as foo + * x + * ``` + */ +function isTypeAssertion( + node: TSESTree.Node | undefined | null, +): node is TSESTree.TSAsExpression | TSESTree.TSTypeAssertion { + if (!node) { + return false; + } + return ( + node.type === AST_NODE_TYPES.TSAsExpression || + node.type === AST_NODE_TYPES.TSTypeAssertion + ); +} + +function isVariableDeclarator( + node: TSESTree.Node | undefined, +): node is TSESTree.VariableDeclarator { + return node?.type === AST_NODE_TYPES.VariableDeclarator; +} + +function isFunction( + node: TSESTree.Node | undefined, +): node is + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression { + if (!node) { + return false; + } + + return [ + AST_NODE_TYPES.ArrowFunctionExpression, + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.FunctionExpression, + ].includes(node.type); +} + +function isFunctionType( + node: TSESTree.Node | undefined, +): node is + | TSESTree.TSCallSignatureDeclaration + | TSESTree.TSConstructorType + | TSESTree.TSConstructSignatureDeclaration + | TSESTree.TSEmptyBodyFunctionExpression + | TSESTree.TSFunctionType + | TSESTree.TSMethodSignature { + if (!node) { + return false; + } + + return [ + AST_NODE_TYPES.TSCallSignatureDeclaration, + AST_NODE_TYPES.TSConstructorType, + AST_NODE_TYPES.TSConstructSignatureDeclaration, + AST_NODE_TYPES.TSEmptyBodyFunctionExpression, + AST_NODE_TYPES.TSFunctionType, + AST_NODE_TYPES.TSMethodSignature, + ].includes(node.type); +} + +function isFunctionOrFunctionType( + node: TSESTree.Node | undefined, +): node is + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.TSCallSignatureDeclaration + | TSESTree.TSConstructorType + | TSESTree.TSConstructSignatureDeclaration + | TSESTree.TSEmptyBodyFunctionExpression + | TSESTree.TSFunctionType + | TSESTree.TSMethodSignature { + return isFunction(node) || isFunctionType(node); +} + +function isTSFunctionType( + node: TSESTree.Node | undefined, +): node is TSESTree.TSFunctionType { + return node?.type === AST_NODE_TYPES.TSFunctionType; +} + +function isTSConstructorType( + node: TSESTree.Node | undefined, +): node is TSESTree.TSConstructorType { + return node?.type === AST_NODE_TYPES.TSConstructorType; +} + +function isClassOrTypeElement( + node: TSESTree.Node | undefined, +): node is TSESTree.ClassElement | TSESTree.TypeElement { + if (!node) { + return false; + } + + return [ + // ClassElement + AST_NODE_TYPES.ClassProperty, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.MethodDefinition, + AST_NODE_TYPES.TSAbstractClassProperty, + AST_NODE_TYPES.TSAbstractMethodDefinition, + AST_NODE_TYPES.TSEmptyBodyFunctionExpression, + AST_NODE_TYPES.TSIndexSignature, + // TypeElement + AST_NODE_TYPES.TSCallSignatureDeclaration, + AST_NODE_TYPES.TSConstructSignatureDeclaration, + // AST_NODE_TYPES.TSIndexSignature, + AST_NODE_TYPES.TSMethodSignature, + AST_NODE_TYPES.TSPropertySignature, + ].includes(node.type); +} + +/** + * Checks if a node is a constructor method. + */ +function isConstructor( + node: TSESTree.Node | undefined, +): node is TSESTree.MethodDefinition { + return ( + node?.type === AST_NODE_TYPES.MethodDefinition && + node.kind === 'constructor' + ); +} + +/** + * Checks if a node is a setter method. + */ +function isSetter( + node: TSESTree.Node | undefined, +): node is TSESTree.MethodDefinition | TSESTree.Property { + return ( + !!node && + (node.type === AST_NODE_TYPES.MethodDefinition || + node.type === AST_NODE_TYPES.Property) && + node.kind === 'set' + ); +} + +function isIdentifier( + node: TSESTree.Node | undefined, +): node is TSESTree.Identifier { + return node?.type === AST_NODE_TYPES.Identifier; +} + +/** + * Checks if a node represents an `await …` expression. + */ +function isAwaitExpression( + node: TSESTree.Node | undefined | null, +): node is TSESTree.AwaitExpression { + return node?.type === AST_NODE_TYPES.AwaitExpression; +} + +/** + * Checks if a possible token is the `await` keyword. + */ +function isAwaitKeyword( + node: TSESTree.Token | TSESTree.Comment | undefined | null, +): node is TSESTree.KeywordToken & { value: 'await' } { + return node?.type === AST_TOKEN_TYPES.Identifier && node.value === 'await'; +} + +function isMemberOrOptionalMemberExpression( + node: TSESTree.Node, +): node is TSESTree.MemberExpression | TSESTree.OptionalMemberExpression { + return ( + node.type === AST_NODE_TYPES.MemberExpression || + node.type === AST_NODE_TYPES.OptionalMemberExpression + ); +} + +export { + isAwaitExpression, + isAwaitKeyword, + isConstructor, + isClassOrTypeElement, + isFunction, + isFunctionOrFunctionType, + isFunctionType, + isIdentifier, + isLogicalOrOperator, + isMemberOrOptionalMemberExpression, + isNonNullAssertionPunctuator, + isNotNonNullAssertionPunctuator, + isNotOptionalChainPunctuator, + isOptionalChainPunctuator, + isOptionalOptionalCallExpression, + isSetter, + isTSConstructorType, + isTSFunctionType, + isTypeAssertion, + isVariableDeclarator, +}; diff --git a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts index 3edb71e5a55..f9278dc8154 100644 --- a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts +++ b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts @@ -13,7 +13,7 @@ type CreateRuleMeta = { docs: CreateRuleMetaDocs; } & Omit, 'docs'>; -export function RuleCreator(urlCreator: (ruleName: string) => string) { +function RuleCreator(urlCreator: (ruleName: string) => string) { // This function will get much easier to call when this is merged https://github.com/Microsoft/TypeScript/pull/26349 // TODO - when the above PR lands; add type checking for the context.report `data` property return function createRule< @@ -52,3 +52,5 @@ export function RuleCreator(urlCreator: (ruleName: string) => string) { }; }; } + +export { RuleCreator }; diff --git a/packages/experimental-utils/src/eslint-utils/applyDefault.ts b/packages/experimental-utils/src/eslint-utils/applyDefault.ts index f9f9c8f3a41..142c8d1a746 100644 --- a/packages/experimental-utils/src/eslint-utils/applyDefault.ts +++ b/packages/experimental-utils/src/eslint-utils/applyDefault.ts @@ -7,7 +7,7 @@ import { deepMerge, isObjectNotArray } from './deepMerge'; * @param userOptions the user opts * @returns the options with defaults */ -export function applyDefault( +function applyDefault( defaultOptions: TDefault, userOptions: TUser | null, ): TDefault { @@ -32,3 +32,5 @@ export function applyDefault( return options; } + +export { applyDefault }; diff --git a/packages/experimental-utils/src/eslint-utils/deepMerge.ts b/packages/experimental-utils/src/eslint-utils/deepMerge.ts index 3603b662a9c..5ac6509203f 100644 --- a/packages/experimental-utils/src/eslint-utils/deepMerge.ts +++ b/packages/experimental-utils/src/eslint-utils/deepMerge.ts @@ -5,7 +5,7 @@ type ObjectLike = Record; * @param obj an object * @returns `true` if obj is an object */ -export function isObjectNotArray( +function isObjectNotArray( obj: unknown | unknown[], ): obj is T { return typeof obj === 'object' && !Array.isArray(obj); @@ -48,3 +48,5 @@ export function deepMerge( return acc; }, {} as ObjectLike); } + +export { isObjectNotArray }; diff --git a/packages/experimental-utils/src/eslint-utils/getParserServices.ts b/packages/experimental-utils/src/eslint-utils/getParserServices.ts index 65607e77290..57b6bc59f65 100644 --- a/packages/experimental-utils/src/eslint-utils/getParserServices.ts +++ b/packages/experimental-utils/src/eslint-utils/getParserServices.ts @@ -1,4 +1,5 @@ -import { ParserServices, TSESLint } from '../'; +import * as TSESLint from '../ts-eslint'; +import { ParserServices } from '../ts-estree'; const ERROR_MESSAGE = 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.'; @@ -10,7 +11,7 @@ type RequiredParserServices = { /** * Try to retrieve typescript parser service from context */ -export function getParserServices< +function getParserServices< TMessageIds extends string, TOptions extends unknown[] >( @@ -29,3 +30,5 @@ export function getParserServices< } return context.parserServices as RequiredParserServices; } + +export { getParserServices }; diff --git a/packages/experimental-utils/src/index.ts b/packages/experimental-utils/src/index.ts index 5a1fdb90bad..b029467e939 100644 --- a/packages/experimental-utils/src/index.ts +++ b/packages/experimental-utils/src/index.ts @@ -1,19 +1,8 @@ +import * as ASTUtils from './ast-utils'; import * as ESLintUtils from './eslint-utils'; import * as TSESLint from './ts-eslint'; import * as TSESLintScope from './ts-eslint-scope'; import * as JSONSchema from './json-schema'; -export { ESLintUtils, JSONSchema, TSESLint, TSESLintScope }; - -// for convenience's sake - export the types directly from here so consumers -// don't need to reference/install both packages in their code - -// NOTE - this uses hard links inside ts-estree to avoid initialization of entire package -// via its main file (which imports typescript at runtime). -// Not every eslint-plugin written in typescript requires typescript at runtime. -export { - AST_NODE_TYPES, - AST_TOKEN_TYPES, - TSESTree, -} from '@typescript-eslint/typescript-estree/dist/ts-estree'; -export { ParserServices } from '@typescript-eslint/typescript-estree/dist/parser-options'; +export { ASTUtils, ESLintUtils, JSONSchema, TSESLint, TSESLintScope }; +export * from './ts-estree'; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Definition.ts b/packages/experimental-utils/src/ts-eslint-scope/Definition.ts index b2f4b91383e..15c69bbf86c 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Definition.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Definition.ts @@ -1,8 +1,8 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import { Definition as ESLintDefinition, ParameterDefinition as ESLintParameterDefinition, } from 'eslint-scope/lib/definition'; +import { TSESTree } from '../ts-estree'; interface Definition { type: string; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Options.ts b/packages/experimental-utils/src/ts-eslint-scope/Options.ts index f06fe4e42e8..a450e225b7f 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Options.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Options.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '../ts-estree'; type PatternVisitorCallback = ( pattern: TSESTree.Identifier, diff --git a/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts b/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts index c53ceb69f7a..8563f6d934b 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts @@ -1,5 +1,5 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import ESLintPatternVisitor from 'eslint-scope/lib/pattern-visitor'; +import { TSESTree } from '../ts-estree'; import { ScopeManager } from './ScopeManager'; import { PatternVisitorCallback, diff --git a/packages/experimental-utils/src/ts-eslint-scope/Reference.ts b/packages/experimental-utils/src/ts-eslint-scope/Reference.ts index 96d2fab4df5..8d5c295f910 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Reference.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Reference.ts @@ -1,5 +1,5 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import ESLintReference from 'eslint-scope/lib/reference'; +import { TSESTree } from '../ts-estree'; import { Scope } from './Scope'; import { Variable } from './Variable'; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts b/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts index 6169d9def9a..1d1180f21f8 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import ESLintReferencer from 'eslint-scope/lib/referencer'; +import { TSESTree } from '../ts-estree'; import { PatternVisitorCallback, PatternVisitorOptions, diff --git a/packages/experimental-utils/src/ts-eslint-scope/Scope.ts b/packages/experimental-utils/src/ts-eslint-scope/Scope.ts index 6c5481aa4d7..49f1e11c795 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Scope.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Scope.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import { Scope as ESLintScope, GlobalScope as ESLintGlobalScope, @@ -14,6 +13,7 @@ import { ForScope as ESLintForScope, ClassScope as ESLintClassScope, } from 'eslint-scope/lib/scope'; +import { TSESTree } from '../ts-estree'; import { Definition } from './Definition'; import { Reference, ReferenceFlag } from './Reference'; import { ScopeManager } from './ScopeManager'; diff --git a/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts b/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts index 6d05fea395f..d1b469a6d4f 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts @@ -1,5 +1,5 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import ESLintScopeManager from 'eslint-scope/lib/scope-manager'; +import { TSESTree } from '../ts-estree'; import { Scope } from './Scope'; import { Variable } from './Variable'; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Variable.ts b/packages/experimental-utils/src/ts-eslint-scope/Variable.ts index 49bbf3d9b4a..c9ff75f95d5 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Variable.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Variable.ts @@ -1,5 +1,5 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import ESLintVariable from 'eslint-scope/lib/variable'; +import { TSESTree } from '../ts-estree'; import { Reference } from './Reference'; import { Definition } from './Definition'; import { Scope } from './Scope'; diff --git a/packages/experimental-utils/src/ts-eslint/AST.ts b/packages/experimental-utils/src/ts-eslint/AST.ts index 1c77caafedf..5a0ea09d843 100644 --- a/packages/experimental-utils/src/ts-eslint/AST.ts +++ b/packages/experimental-utils/src/ts-eslint/AST.ts @@ -1,9 +1,6 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { - TSESTree, - AST_TOKEN_TYPES, -} from '@typescript-eslint/typescript-estree'; +import { TSESTree, AST_TOKEN_TYPES } from '../ts-estree'; namespace AST { export type TokenType = AST_TOKEN_TYPES; diff --git a/packages/experimental-utils/src/ts-eslint/Linter.ts b/packages/experimental-utils/src/ts-eslint/Linter.ts index 93c5565ff4d..0233f2f8408 100644 --- a/packages/experimental-utils/src/ts-eslint/Linter.ts +++ b/packages/experimental-utils/src/ts-eslint/Linter.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { TSESTree, ParserServices } from '@typescript-eslint/typescript-estree'; import { Linter as ESLintLinter } from 'eslint'; +import { TSESTree, ParserServices } from '../ts-estree'; import { ParserOptions as TSParserOptions } from './ParserOptions'; import { RuleModule, RuleFix } from './Rule'; import { Scope } from './Scope'; diff --git a/packages/experimental-utils/src/ts-eslint/ParserOptions.ts b/packages/experimental-utils/src/ts-eslint/ParserOptions.ts index 12c121989dc..d6bf57bb585 100644 --- a/packages/experimental-utils/src/ts-eslint/ParserOptions.ts +++ b/packages/experimental-utils/src/ts-eslint/ParserOptions.ts @@ -1,6 +1,6 @@ import { TSESTreeOptions } from '@typescript-eslint/typescript-estree'; -export interface ParserOptions { +interface ParserOptions { comment?: boolean; ecmaFeatures?: { globalReturn?: boolean; @@ -23,3 +23,5 @@ export interface ParserOptions { useJSXTextNode?: boolean; warnOnUnsupportedTypeScriptVersion?: boolean; } + +export { ParserOptions }; diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts index 6e7733da663..1f74150292e 100644 --- a/packages/experimental-utils/src/ts-eslint/Rule.ts +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -1,5 +1,5 @@ -import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; import { JSONSchema4 } from '../json-schema'; +import { ParserServices, TSESTree } from '../ts-estree'; import { AST } from './AST'; import { Linter } from './Linter'; import { Scope } from './Scope'; diff --git a/packages/experimental-utils/src/ts-eslint/RuleTester.ts b/packages/experimental-utils/src/ts-eslint/RuleTester.ts index ce441603bf1..9c63709f37f 100644 --- a/packages/experimental-utils/src/ts-eslint/RuleTester.ts +++ b/packages/experimental-utils/src/ts-eslint/RuleTester.ts @@ -1,8 +1,5 @@ -import { - AST_NODE_TYPES, - AST_TOKEN_TYPES, -} from '@typescript-eslint/typescript-estree'; import { RuleTester as ESLintRuleTester } from 'eslint'; +import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '../ts-estree'; import { ParserOptions } from './ParserOptions'; import { RuleModule } from './Rule'; diff --git a/packages/experimental-utils/src/ts-eslint/Scope.ts b/packages/experimental-utils/src/ts-eslint/Scope.ts index 03c6bc66395..bb5e5accc51 100644 --- a/packages/experimental-utils/src/ts-eslint/Scope.ts +++ b/packages/experimental-utils/src/ts-eslint/Scope.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '../ts-estree'; namespace Scope { export interface ScopeManager { diff --git a/packages/experimental-utils/src/ts-eslint/SourceCode.ts b/packages/experimental-utils/src/ts-eslint/SourceCode.ts index eeaa906f37d..0ea39973a25 100644 --- a/packages/experimental-utils/src/ts-eslint/SourceCode.ts +++ b/packages/experimental-utils/src/ts-eslint/SourceCode.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; import { SourceCode as ESLintSourceCode } from 'eslint'; +import { ParserServices, TSESTree } from '../ts-estree'; import { Scope } from './Scope'; -declare interface SourceCode { +interface SourceCode { text: string; ast: SourceCode.Program; lines: string[]; diff --git a/packages/experimental-utils/src/ts-estree.ts b/packages/experimental-utils/src/ts-estree.ts new file mode 100644 index 00000000000..a7f18377e37 --- /dev/null +++ b/packages/experimental-utils/src/ts-estree.ts @@ -0,0 +1,12 @@ +// for convenience's sake - export the types directly from here so consumers +// don't need to reference/install both packages in their code + +// NOTE - this uses hard links inside ts-estree to avoid initialization of entire package +// via its main file (which imports typescript at runtime). +// Not every eslint-plugin written in typescript requires typescript at runtime. +export { + AST_NODE_TYPES, + AST_TOKEN_TYPES, + TSESTree, +} from '@typescript-eslint/typescript-estree/dist/ts-estree'; +export { ParserServices } from '@typescript-eslint/typescript-estree/dist/parser-options'; diff --git a/packages/experimental-utils/typings/eslint-utils.d.ts b/packages/experimental-utils/typings/eslint-utils.d.ts new file mode 100644 index 00000000000..f12326c0b4a --- /dev/null +++ b/packages/experimental-utils/typings/eslint-utils.d.ts @@ -0,0 +1,40 @@ +declare module 'eslint-utils' { + export const findVariable: unknown; + export const getFunctionHeadLocation: unknown; + export const getFunctionNameWithKind: unknown; + export const getInnermostScope: unknown; + export const getPropertyName: unknown; + export const getStaticValue: unknown; + export const getStringIfConstant: unknown; + export const hasSideEffect: unknown; + export const isArrowToken: unknown; + export const isClosingBraceToken: unknown; + export const isClosingBracketToken: unknown; + export const isClosingParenToken: unknown; + export const isColonToken: unknown; + export const isCommaToken: unknown; + export const isCommentToken: unknown; + export const isNotArrowToken: unknown; + export const isNotClosingBraceToken: unknown; + export const isNotClosingBracketToken: unknown; + export const isNotClosingParenToken: unknown; + export const isNotColonToken: unknown; + export const isNotCommaToken: unknown; + export const isNotCommentToken: unknown; + export const isNotOpeningBraceToken: unknown; + export const isNotOpeningBracketToken: unknown; + export const isNotOpeningParenToken: unknown; + export const isNotSemicolonToken: unknown; + export const isOpeningBraceToken: unknown; + export const isOpeningBracketToken: unknown; + export const isOpeningParenToken: unknown; + export const isParenthesized: unknown; + export const isSemicolonToken: unknown; + export const PatternMatcher: unknown; + export const ReferenceTracker: { + READ: never; + CALL: never; + CONSTRUCT: never; + new (): never; + }; +} diff --git a/yarn.lock b/yarn.lock index 05923ed503f..1d684478a1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3373,6 +3373,13 @@ eslint-utils@^1.4.3: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" + integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA== + dependencies: + eslint-visitor-keys "^1.1.0" + eslint-visitor-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" From fec9656ce69909f51a9a495038d4e33cc7cd4970 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Tue, 3 Mar 2020 23:03:49 -0800 Subject: [PATCH 2/2] docs: update experimental-utils docs --- packages/experimental-utils/README.md | 23 +++++++++++++---------- packages/experimental-utils/src/index.ts | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/experimental-utils/README.md b/packages/experimental-utils/README.md index 2f02ef10615..97d9d5c057b 100644 --- a/packages/experimental-utils/README.md +++ b/packages/experimental-utils/README.md @@ -5,23 +5,26 @@ ## Note This package has inherited its version number from the `@typescript-eslint` project. -Meaning that even though this package is `1.x.y`, you shouldn't expect 100% stability between minor version bumps. +Meaning that even though this package is `2.x.y`, you shouldn't expect 100% stability between minor version bumps. i.e. treat it as a `0.x.y` package. Feel free to use it now, and let us know what utilities you need or send us PRs with utilities you build on top of it. -Once it is stable, it will be renamed to `@typescript-eslint/util` for a `2.0.0` release. +Once it is stable, it will be renamed to `@typescript-eslint/util` for a `3.0.0` release. ## Exports -| Name | Description | -| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -| [`TSESTree`](../packages/typescript-estree/src/ts-estree/ts-estree.ts) | Types for the TypeScript flavor of ESTree created by `@typescript-eslint/typescript-estree`. | -| [`AST_NODE_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) | An enum with the names of every single _node_ found in `TSESTree`. | -| [`AST_TOKEN_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) | An enum with the names of every single _token_ found in `TSESTree`. | -| [`TSESLint`](./src/ts-eslint) | Types for ESLint, correctly typed to work with the types found in `TSESTree`. | -| [`ESLintUtils`](./src/eslint-utils) | Tools for creating ESLint rules with TypeScript. | -| [`ParserServices`](../packages/typescript-estree/src/ts-estree/parser.ts) | The parser services provided when parsing a file using `@typescript-eslint/typescript-estree`. | +| Name | Description | +| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`ASTUtils`](./src/ast-utils) | Tools for operating on the ESTree AST. Also includes the [`eslint-utils`](https://www.npmjs.com/package/eslint-utils) package, correctly typed to work with the types found in `TSESTree` | +| [`ESLintUtils`](./src/eslint-utils) | Tools for creating ESLint rules with TypeScript. | +| `JSONSchema` | Types from the [`@types/json-schema`](https://www.npmjs.com/package/@types/json-schema) package, re-exported to save you having to manually import them. Also ensures you're using the same version of the types as this package. | +| [`TSESLint`](./src/ts-eslint) | Types for ESLint, correctly typed to work with the types found in `TSESTree`. | +| [`TSESLintScope`](./src/ts-eslint-scope) | The [`eslint-scope`](https://www.npmjs.com/package/eslint-scope) package, correctly typed to work with the types found in both `TSESTree` and `TSESLint` | +| [`TSESTree`](../packages/typescript-estree/src/ts-estree/ts-estree.ts) | Types for the TypeScript flavor of ESTree created by `@typescript-eslint/typescript-estree`. | +| [`AST_NODE_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) | An enum with the names of every single _node_ found in `TSESTree`. | +| [`AST_TOKEN_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) | An enum with the names of every single _token_ found in `TSESTree`. | +| [`ParserServices`](../packages/typescript-estree/src/ts-estree/parser.ts) | Typing for the parser services provided when parsing a file using `@typescript-eslint/typescript-estree`. | ## Contributing diff --git a/packages/experimental-utils/src/index.ts b/packages/experimental-utils/src/index.ts index b029467e939..31328386269 100644 --- a/packages/experimental-utils/src/index.ts +++ b/packages/experimental-utils/src/index.ts @@ -1,8 +1,8 @@ import * as ASTUtils from './ast-utils'; import * as ESLintUtils from './eslint-utils'; +import * as JSONSchema from './json-schema'; import * as TSESLint from './ts-eslint'; import * as TSESLintScope from './ts-eslint-scope'; -import * as JSONSchema from './json-schema'; export { ASTUtils, ESLintUtils, JSONSchema, TSESLint, TSESLintScope }; export * from './ts-estree';