From 44c6e8f72aa3bcb94e64984e4ff1e0711c5f8b7f Mon Sep 17 00:00:00 2001 From: Wenlu Wang Date: Fri, 15 Jan 2021 14:13:49 +0800 Subject: [PATCH] add config to control assume function is constant or not --- package.json | 2 +- plugin/package.json | 7 +- plugin/src/index.ts | 4 +- src/resolver.ts | 767 +++++++++--------- src/service.ts | 13 +- src/types.ts | 1 + tests/project/cases/common.ts | 6 + .../cases/configs/preferConstantCall.tsx | 11 + ...ferFullAccess.tsx => preferFullAccess.tsx} | 0 .../cases/configs/preferImmutableCall1.tsx | 28 +- .../cases/configs/preferImmutableCall2.tsx | 30 +- .../constants/shouldWorkWithLiteral1.tsx | 24 +- .../constants/shouldWorkWithLiteral10.tsx | 24 +- .../constants/shouldWorkWithLiteral11.tsx | 24 +- .../constants/shouldWorkWithLiteral12.tsx | 24 +- .../constants/shouldWorkWithLiteral13.tsx | 24 +- .../constants/shouldWorkWithLiteral2.tsx | 24 +- .../constants/shouldWorkWithLiteral3.tsx | 24 +- .../constants/shouldWorkWithLiteral4.tsx | 24 +- .../constants/shouldWorkWithLiteral5.tsx | 26 +- .../constants/shouldWorkWithLiteral6.tsx | 24 +- .../constants/shouldWorkWithLiteral7.tsx | 26 +- .../constants/shouldWorkWithLiteral8.tsx | 24 +- .../constants/shouldWorkWithLiteral9.tsx | 24 +- tests/suite/cases/classic/configs.test.ts | 153 ++-- tests/suite/cases/classic/constants.test.ts | 484 +++++------ tests/suite/tesUtils.ts | 11 + 27 files changed, 942 insertions(+), 891 deletions(-) create mode 100644 tests/project/cases/configs/preferConstantCall.tsx rename tests/project/cases/configs/{perferFullAccess.tsx => preferFullAccess.tsx} (100%) diff --git a/package.json b/package.json index 811f3c4..43a5c70 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "type": "git", "url": "https://github.com/HearTao/ts-react-hooks-tools.git" }, - "version": "0.1.16", + "version": "0.1.17", "main": "./dist/index.js", "types": "./dist/index.d.ts", "scripts": { diff --git a/plugin/package.json b/plugin/package.json index 51cedd5..2cc72cd 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -3,7 +3,7 @@ "displayName": "Typescript React hooks Tools", "description": "Useful refactor of React hooks with TypeScript.", "publisher": "kingwl", - "version": "0.1.16", + "version": "0.1.17", "license": "MIT", "icon": "img/logo.png", "repository": { @@ -42,6 +42,11 @@ "type": "boolean", "description": "Control whether if collect whole call expression as dependencies", "default": true + }, + "trht.preferConstantCall": { + "type": "boolean", + "description": "Control whether if assume call expression is constants", + "default": true } } } diff --git a/plugin/src/index.ts b/plugin/src/index.ts index 370cc69..f317199 100644 --- a/plugin/src/index.ts +++ b/plugin/src/index.ts @@ -6,7 +6,8 @@ const configurationSection = 'trht'; interface SynchronizedConfiguration { preferFullAccess?: boolean; - preferImmutableCall?: boolean + preferImmutableCall?: boolean; + preferConstantCall?: boolean; } export async function activate(context: vscode.ExtensionContext) { @@ -43,6 +44,7 @@ function getConfiguration(): SynchronizedConfiguration { withConfigValue(config, outConfig, 'preferFullAccess'); withConfigValue(config, outConfig, 'preferImmutableCall'); + withConfigValue(config, outConfig, 'preferConstantCall'); return outConfig; } diff --git a/src/resolver.ts b/src/resolver.ts index 37c58bf..2ab7a67 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -1,375 +1,392 @@ -import type * as ts from 'typescript/lib/tsserverlibrary'; -import { Constants } from './constants'; -import { first } from './helper'; -import { DepSymbolResolver, FunctionExpressionLike } from './types'; -import { - isDeclarationAssignedByConstantsUseCallback, - isDeclarationAssignedByConstantsUseMemo, - isDeclarationAssignedByUseReducer, - isDeclarationAssignedByUseRef, - isDeclarationAssignedByUseState, - isFunctionExpressionLike, - isTopLevelConstantDeclaration, - skipSymbolAlias -} from './utils'; - -export function isInTypeContext(typescript: typeof ts, node: ts.Node) { - return typescript.isTypeElement(node) || typescript.isPartOfTypeNode(node); -} - -export function createDepSymbolResolver( - typescript: typeof ts, - scope: ts.Node, - additionalScope: readonly ts.Node[], - file: ts.SourceFile, - checker: ts.TypeChecker -): DepSymbolResolver { - const cached = new Map(); - const duplicated = new Set(); - const accessExpressionContainsInnerCached = new Map(); - - return { - shouldSymbolDefinitelyBeIgnoreInDeps, - alreadyDuplicated, - markAsDuplicated, - isExpressionContainsDeclaredInner, - markExpressionContainsDeclaredInner - }; - - function isWellKnownGlobalSymbol(symbol: ts.Symbol) { - return ( - checker.isUndefinedSymbol(symbol) || - checker.isArgumentsSymbol(symbol) - ); - } - - function markExpressionContainsDeclaredInner( - expr: ts.Node, - value: boolean - ) { - if (accessExpressionContainsInnerCached.has(expr)) { - return; - } - accessExpressionContainsInnerCached.set(expr, value); - } - - function isDeclarationContainsInnerFile(valueDeclaration: ts.Declaration) { - if (valueDeclaration.getSourceFile() !== file) { - return false; - } - - if (typescript.rangeContainsRange(scope, valueDeclaration)) { - return true; - } - - if ( - additionalScope.some(s => - typescript.rangeContainsRange(s, valueDeclaration) - ) - ) { - return true; - } - return false; - } - - function isExpressionContainsDeclaredInner(expr: ts.Node) { - return visitor(expr); - - function visitor(node: ts.Node): boolean | undefined { - if (!accessExpressionContainsInnerCached.has(node)) { - if (typescript.isTypeNode(node)) { - return undefined; - } - - if (typescript.isIdentifier(node)) { - const rawSymbol = checker.getSymbolAtLocation(node); - const symbol = - rawSymbol && - skipSymbolAlias(typescript, rawSymbol, checker); - if ( - symbol && - !isWellKnownGlobalSymbol(symbol) && - symbol.valueDeclaration - ) { - const result = isDeclarationContainsInnerFile( - symbol.valueDeclaration - ); - accessExpressionContainsInnerCached.set(node, result); - return result; - } - accessExpressionContainsInnerCached.set(node, false); - return false; - } - const result = typescript.forEachChild(node, visitor); - accessExpressionContainsInnerCached.set(node, !!result); - return result; - } - return accessExpressionContainsInnerCached.get(node); - } - } - - function peekSymbolDefinitelyBeIgnoreInDeps(rawSymbol: ts.Symbol) { - if (isWellKnownGlobalSymbol(rawSymbol)) { - return true; - } - - const symbol = skipSymbolAlias(typescript, rawSymbol, checker); - const valueDeclaration = symbol.valueDeclaration; - if (!valueDeclaration) { - return false; - } - - const declFile = valueDeclaration.getSourceFile(); - if (declFile.isDeclarationFile) { - return true; - } - - const inTypeContext = isInTypeContext(typescript, valueDeclaration); - if (inTypeContext) { - return false; - } - - if (declFile !== file) { - return true; - } - - if (isDeclarationContainsInnerFile(valueDeclaration)) { - return true; - } - - if ( - isDeclarationAssignedByUseRef(typescript, valueDeclaration) || - isDeclarationAssignedByUseState(typescript, valueDeclaration) || - isDeclarationAssignedByUseReducer(typescript, valueDeclaration) || - isDeclarationAssignedByConstantsUseMemo( - typescript, - valueDeclaration - ) || - isDeclarationAssignedByConstantsUseCallback( - typescript, - valueDeclaration - ) - ) { - return true; - } - - if (isDeclarationConstants(valueDeclaration)) { - return true; - } - - if (isTopLevelConstantDeclaration(typescript, valueDeclaration)) { - return true; - } - return false; - - function isExpressionConstants(node: ts.Expression) { - if (typescript.isLiteralExpression(node)) { - return true; - } - - if ( - typescript.isParenthesizedExpression(node) || - typescript.isNonNullExpression(node) - ) { - return isParenExpressionOrNonNullExpressionConstants(node); - } - if (typescript.isIdentifier(node)) { - return isIdentifierConstants(node); - } - if (typescript.isPropertyAccessExpression(node)) { - return isPropertyAccessConstants(node); - } - if (typescript.isElementAccessExpression(node)) { - return isElementAccessConstants(node); - } - if (typescript.isTemplateExpression(node)) { - return isTemplateLiteralConstants(node); - } - if (typescript.isBinaryExpression(node)) { - return isBinaryExpressionConstants(node); - } - if ( - typescript.isPrefixUnaryExpression(node) || - typescript.isPostfixUnaryExpression(node) - ) { - return isUpdateExpressionConstant(node); - } - if (typescript.isCallExpression(node)) { - return isCallExpressionConstants(node); - } - if (isFunctionExpressionLike(typescript, node)) { - return isSimpleFunctionLikeConstants(node); - } - return false; - } - - function isDeclarationConstants(decl: ts.Declaration): boolean { - if ( - typescript.isEnumDeclaration(decl) || - typescript.isEnumMember(decl) || - typescript.isModuleDeclaration(decl) - ) { - return true; - } - if ( - typescript.isVariableDeclaration(decl) && - typescript.getCombinedNodeFlags(decl) & - typescript.NodeFlags.Const && - decl.initializer && - isExpressionConstants(decl.initializer) - ) { - return true; - } - if (isFunctionExpressionLike(typescript, decl)) { - return isSimpleFunctionLikeConstants(decl); - } - return false; - } - - function isParenExpressionOrNonNullExpressionConstants( - expression: ts.ParenthesizedExpression | ts.NonNullExpression - ): boolean { - return isExpressionConstants(expression.expression); - } - - function isSimpleFunctionLikeConstants( - func: FunctionExpressionLike - ): boolean { - if (func.parameters.length !== 0) { - return false; - } - if (typescript.isBlock(func.body)) { - const effectiveStatement = func.body.statements.filter(stmt => - typescript.isEmptyStatement(stmt) - ); - if (effectiveStatement.length === 1) { - const firstStatement = first(effectiveStatement); - if ( - typescript.isReturnStatement(firstStatement) && - firstStatement.expression - ) { - return isExpressionConstants(firstStatement.expression); - } - } - } else { - return isExpressionConstants(func.body); - } - return false; - } - - function isCallExpressionConstants(call: ts.CallExpression): boolean { - if (call.arguments.length) { - return false; - } - - return isExpressionConstants(call.expression); - } - - function isBinaryExpressionConstants( - expression: ts.BinaryExpression - ): boolean { - if (typescript.isAssignmentExpression(expression)) { - return false; - } - return ( - isExpressionConstants(expression.left) && - isExpressionConstants(expression.right) - ); - } - - function isUpdateExpressionConstant( - expression: ts.PrefixUnaryExpression | ts.PostfixUnaryExpression - ): boolean { - if ( - expression.operator === typescript.SyntaxKind.PlusPlusToken || - expression.operator === typescript.SyntaxKind.MinusMinusToken - ) { - return false; - } - return isExpressionConstants(expression.operand); - } - - function isIdentifierImmutable(identifier: ts.Identifier) { - return ( - identifier.text === Constants.UndefinedKeyword && - identifier.originalKeywordKind === - typescript.SyntaxKind.UndefinedKeyword - ); - } - - function isIdentifierConstants(expression: ts.Identifier): boolean { - if (isIdentifierImmutable(expression)) { - return true; - } - const symbol = checker.getSymbolAtLocation(expression); - return !!(symbol && shouldSymbolDefinitelyBeIgnoreInDeps(symbol)); - } - - function isLiteralImmutable(expression: ts.Expression) { - switch (expression.kind) { - case typescript.SyntaxKind.StringLiteral: - case typescript.SyntaxKind.NoSubstitutionTemplateLiteral: - case typescript.SyntaxKind.NumericLiteral: - case typescript.SyntaxKind.TrueKeyword: - case typescript.SyntaxKind.FalseKeyword: - case typescript.SyntaxKind.NullKeyword: - case typescript.SyntaxKind.RegularExpressionLiteral: - return true; - case typescript.SyntaxKind.Identifier: - return isIdentifierImmutable(expression as ts.Identifier); - default: - return false; - } - } - - function isPropertyAccessConstants( - expression: ts.PropertyAccessExpression - ): boolean { - const symbol = checker.getSymbolAtLocation(expression); - if (symbol && shouldSymbolDefinitelyBeIgnoreInDeps(symbol)) { - return true; - } - return isLiteralImmutable(expression.expression); - } - - function isElementAccessConstants( - expression: ts.ElementAccessExpression - ): boolean { - const symbol = checker.getSymbolAtLocation(expression); - if (symbol && shouldSymbolDefinitelyBeIgnoreInDeps(symbol)) { - return true; - } - return ( - isLiteralImmutable(expression.expression) && - isExpressionConstants(expression.argumentExpression) - ); - } - - function isTemplateLiteralConstants( - expression: ts.TemplateExpression - ): boolean { - return expression.templateSpans.every(span => - isExpressionConstants(span.expression) - ); - } - } - - function shouldSymbolDefinitelyBeIgnoreInDeps(rawSymbol: ts.Symbol) { - if (!cached.has(rawSymbol)) { - cached.set( - rawSymbol, - peekSymbolDefinitelyBeIgnoreInDeps(rawSymbol) - ); - } - - return cached.get(rawSymbol); - } - - function alreadyDuplicated(symbol: ts.Symbol) { - return duplicated.has(symbol); - } - - function markAsDuplicated(symbol: ts.Symbol) { - duplicated.add(symbol); - } -} +import type * as ts from 'typescript/lib/tsserverlibrary'; +import { Constants } from './constants'; +import { cast, first } from './helper'; +import { LanguageServiceLogger } from './logger'; +import { DepSymbolResolver, FunctionExpressionLike } from './types'; +import { + isDeclarationAssignedByConstantsUseCallback, + isDeclarationAssignedByConstantsUseMemo, + isDeclarationAssignedByUseReducer, + isDeclarationAssignedByUseRef, + isDeclarationAssignedByUseState, + isFunctionExpressionLike, + isTopLevelConstantDeclaration, + skipSymbolAlias +} from './utils'; + +export function isInTypeContext(typescript: typeof ts, node: ts.Node) { + return typescript.isTypeElement(node) || typescript.isPartOfTypeNode(node); +} + +export function createDepSymbolResolver( + typescript: typeof ts, + scope: ts.Node, + additionalScope: readonly ts.Node[], + file: ts.SourceFile, + checker: ts.TypeChecker, + preferConstantsCall: boolean, + logger: LanguageServiceLogger +): DepSymbolResolver { + const cached = new Map(); + const duplicated = new Set(); + const accessExpressionContainsInnerCached = new Map(); + + return { + shouldSymbolDefinitelyBeIgnoreInDeps, + alreadyDuplicated, + markAsDuplicated, + isExpressionContainsDeclaredInner, + markExpressionContainsDeclaredInner + }; + + function isWellKnownGlobalSymbol(symbol: ts.Symbol) { + return ( + checker.isUndefinedSymbol(symbol) || + checker.isArgumentsSymbol(symbol) + ); + } + + function markExpressionContainsDeclaredInner( + expr: ts.Node, + value: boolean + ) { + if (accessExpressionContainsInnerCached.has(expr)) { + return; + } + accessExpressionContainsInnerCached.set(expr, value); + } + + function isDeclarationContainsInnerFile(valueDeclaration: ts.Declaration) { + if (valueDeclaration.getSourceFile() !== file) { + return false; + } + + if (typescript.rangeContainsRange(scope, valueDeclaration)) { + return true; + } + + if ( + additionalScope.some(s => + typescript.rangeContainsRange(s, valueDeclaration) + ) + ) { + return true; + } + return false; + } + + function isExpressionContainsDeclaredInner(expr: ts.Node) { + return visitor(expr); + + function visitor(node: ts.Node): boolean | undefined { + if (!accessExpressionContainsInnerCached.has(node)) { + if (typescript.isTypeNode(node)) { + return undefined; + } + + if (typescript.isIdentifier(node)) { + const rawSymbol = checker.getSymbolAtLocation(node); + const symbol = + rawSymbol && + skipSymbolAlias(typescript, rawSymbol, checker); + if ( + symbol && + !isWellKnownGlobalSymbol(symbol) && + symbol.valueDeclaration + ) { + const result = isDeclarationContainsInnerFile( + symbol.valueDeclaration + ); + accessExpressionContainsInnerCached.set(node, result); + return result; + } + accessExpressionContainsInnerCached.set(node, false); + return false; + } + const result = typescript.forEachChild(node, visitor); + accessExpressionContainsInnerCached.set(node, !!result); + return result; + } + return accessExpressionContainsInnerCached.get(node); + } + } + + function peekSymbolDefinitelyBeIgnoreInDeps(rawSymbol: ts.Symbol) { + if (isWellKnownGlobalSymbol(rawSymbol)) { + return true; + } + + const symbol = skipSymbolAlias(typescript, rawSymbol, checker); + const valueDeclaration = symbol.valueDeclaration; + if (!valueDeclaration) { + return false; + } + logger.log('WTF 3'); + + const declFile = valueDeclaration.getSourceFile(); + if (declFile.isDeclarationFile) { + return true; + } + + const inTypeContext = isInTypeContext(typescript, valueDeclaration); + if (inTypeContext) { + return false; + } + logger.log('WTF 4'); + + if (declFile !== file) { + return true; + } + + if (isDeclarationContainsInnerFile(valueDeclaration)) { + return true; + } + logger.log('WTF 5'); + + if ( + isDeclarationAssignedByUseRef(typescript, valueDeclaration) || + isDeclarationAssignedByUseState(typescript, valueDeclaration) || + isDeclarationAssignedByUseReducer(typescript, valueDeclaration) || + isDeclarationAssignedByConstantsUseMemo( + typescript, + valueDeclaration + ) || + isDeclarationAssignedByConstantsUseCallback( + typescript, + valueDeclaration + ) + ) { + return true; + } + logger.log('WTF 6'); + + if (isDeclarationConstants(valueDeclaration)) { + return true; + } + logger.log('WTF 7'); + + if (isTopLevelConstantDeclaration(typescript, valueDeclaration)) { + return true; + } + logger.log('WTF 8'); + return false; + + function isExpressionConstants(node: ts.Expression) { + if (typescript.isLiteralExpression(node)) { + return true; + } + + if ( + typescript.isParenthesizedExpression(node) || + typescript.isNonNullExpression(node) + ) { + return isParenExpressionOrNonNullExpressionConstants(node); + } + if (typescript.isIdentifier(node)) { + return isIdentifierConstants(node); + } + if (typescript.isPropertyAccessExpression(node)) { + return isPropertyAccessConstants(node); + } + if (typescript.isElementAccessExpression(node)) { + return isElementAccessConstants(node); + } + if (typescript.isTemplateExpression(node)) { + return isTemplateLiteralConstants(node); + } + if (typescript.isBinaryExpression(node)) { + return isBinaryExpressionConstants(node); + } + if ( + typescript.isPrefixUnaryExpression(node) || + typescript.isPostfixUnaryExpression(node) + ) { + return isUpdateExpressionConstant(node); + } + if (typescript.isCallExpression(node)) { + return isCallExpressionConstants(node); + } + if (isFunctionExpressionLike(typescript, node)) { + return isSimpleFunctionLikeConstants(node); + } + return false; + } + + function isDeclarationConstants(decl: ts.Declaration): boolean { + if ( + typescript.isEnumDeclaration(decl) || + typescript.isEnumMember(decl) || + typescript.isModuleDeclaration(decl) + ) { + return true; + } + if ( + typescript.isVariableDeclaration(decl) && + typescript.getCombinedNodeFlags(decl) & + typescript.NodeFlags.Const && + decl.initializer && + isExpressionConstants(decl.initializer) + ) { + return true; + } + if (isFunctionExpressionLike(typescript, decl)) { + return isSimpleFunctionLikeConstants(decl); + } + return false; + } + + function isParenExpressionOrNonNullExpressionConstants( + expression: ts.ParenthesizedExpression | ts.NonNullExpression + ): boolean { + return isExpressionConstants(expression.expression); + } + + function isSimpleFunctionLikeConstants( + func: FunctionExpressionLike + ): boolean { + if (func.parameters.length !== 0) { + return false; + } + if (typescript.isBlock(func.body)) { + const effectiveStatement = func.body.statements.filter(stmt => + typescript.isEmptyStatement(stmt) + ); + if (effectiveStatement.length === 1) { + const firstStatement = first(effectiveStatement); + if ( + typescript.isReturnStatement(firstStatement) && + firstStatement.expression + ) { + return isExpressionConstants(firstStatement.expression); + } + } + } else { + return isExpressionConstants(func.body); + } + return false; + } + + function isCallExpressionConstants(call: ts.CallExpression): boolean { + logger.log('isCallExpressionConstants: ' + call.getText(file)); + logger.log('isCallExpressionConstants: ' + preferConstantsCall); + if (!preferConstantsCall) { + return false; + } + + logger.log('isCallExpressionConstants: ' + 'check arguments'); + if (!call.arguments.every(isExpressionConstants)) { + return false; + } + + logger.log('isCallExpressionConstants: ' + 'check expression'); + return isExpressionConstants(call.expression); + } + + function isBinaryExpressionConstants( + expression: ts.BinaryExpression + ): boolean { + if (typescript.isAssignmentExpression(expression)) { + return false; + } + return ( + isExpressionConstants(expression.left) && + isExpressionConstants(expression.right) + ); + } + + function isUpdateExpressionConstant( + expression: ts.PrefixUnaryExpression | ts.PostfixUnaryExpression + ): boolean { + if ( + expression.operator === typescript.SyntaxKind.PlusPlusToken || + expression.operator === typescript.SyntaxKind.MinusMinusToken + ) { + return false; + } + return isExpressionConstants(expression.operand); + } + + function isIdentifierImmutable(identifier: ts.Identifier) { + return ( + identifier.text === Constants.UndefinedKeyword && + identifier.originalKeywordKind === + typescript.SyntaxKind.UndefinedKeyword + ); + } + + function isIdentifierConstants(expression: ts.Identifier): boolean { + if (isIdentifierImmutable(expression)) { + return true; + } + const symbol = checker.getSymbolAtLocation(expression); + return !!(symbol && shouldSymbolDefinitelyBeIgnoreInDeps(symbol)); + } + + function isLiteralImmutable(expression: ts.Expression) { + switch (expression.kind) { + case typescript.SyntaxKind.StringLiteral: + case typescript.SyntaxKind.NoSubstitutionTemplateLiteral: + case typescript.SyntaxKind.NumericLiteral: + case typescript.SyntaxKind.TrueKeyword: + case typescript.SyntaxKind.FalseKeyword: + case typescript.SyntaxKind.NullKeyword: + case typescript.SyntaxKind.RegularExpressionLiteral: + return true; + case typescript.SyntaxKind.Identifier: + return isIdentifierImmutable(expression as ts.Identifier); + default: + return false; + } + } + + function isPropertyAccessConstants( + expression: ts.PropertyAccessExpression + ): boolean { + const symbol = checker.getSymbolAtLocation(expression); + if (symbol && shouldSymbolDefinitelyBeIgnoreInDeps(symbol)) { + return true; + } + return isLiteralImmutable(expression.expression); + } + + function isElementAccessConstants( + expression: ts.ElementAccessExpression + ): boolean { + const symbol = checker.getSymbolAtLocation(expression); + if (symbol && shouldSymbolDefinitelyBeIgnoreInDeps(symbol)) { + return true; + } + return ( + isLiteralImmutable(expression.expression) && + isExpressionConstants(expression.argumentExpression) + ); + } + + function isTemplateLiteralConstants( + expression: ts.TemplateExpression + ): boolean { + return expression.templateSpans.every(span => + isExpressionConstants(span.expression) + ); + } + } + + function shouldSymbolDefinitelyBeIgnoreInDeps(rawSymbol: ts.Symbol) { + if (!cached.has(rawSymbol)) { + cached.set( + rawSymbol, + peekSymbolDefinitelyBeIgnoreInDeps(rawSymbol) + ); + } + + return cached.get(rawSymbol); + } + + function alreadyDuplicated(symbol: ts.Symbol) { + return duplicated.has(symbol); + } + + function markAsDuplicated(symbol: ts.Symbol) { + duplicated.add(symbol); + } +} diff --git a/src/service.ts b/src/service.ts index f349bd7..69cf163 100644 --- a/src/service.ts +++ b/src/service.ts @@ -276,7 +276,8 @@ export class CustomizedLanguageService implements ICustomizedLanguageServie { file, checker, this.configManager.config.preferFullAccess, - this.configManager.config.preferImmutableCall + this.configManager.config.preferImmutableCall, + this.configManager.config.preferConstantCall ) : []; const hooksReference = full @@ -311,7 +312,8 @@ export class CustomizedLanguageService implements ICustomizedLanguageServie { file, checker, this.configManager.config.preferFullAccess, - this.configManager.config.preferImmutableCall + this.configManager.config.preferImmutableCall, + this.configManager.config.preferConstantCall ) : []; const hooksReference = full @@ -532,7 +534,8 @@ export class CustomizedLanguageService implements ICustomizedLanguageServie { file: ts.SourceFile, checker: ts.TypeChecker, preferFullAccess: boolean = true, - preferImmutableCall: boolean = true + preferImmutableCall: boolean = true, + preferConstantCall: boolean = true ) { const ts = this.typescript; const logger = this.logger; @@ -542,7 +545,9 @@ export class CustomizedLanguageService implements ICustomizedLanguageServie { scope, additionalScope, file, - checker + checker, + preferConstantCall, + logger ); visitor(scope); diff --git a/src/types.ts b/src/types.ts index 3f0d401..5416259 100644 --- a/src/types.ts +++ b/src/types.ts @@ -76,6 +76,7 @@ export type HooksReferenceNameType = export interface SynchronizedConfiguration { preferFullAccess?: boolean; preferImmutableCall?: boolean; + preferConstantCall?: boolean; } export interface DepSymbolResolver { diff --git a/tests/project/cases/common.ts b/tests/project/cases/common.ts index b37cbbd..74f52b4 100644 --- a/tests/project/cases/common.ts +++ b/tests/project/cases/common.ts @@ -13,3 +13,9 @@ export interface CommonUnknownProps { foo: any; }; } + +let id = 0; + +export function getId(): { id: number } { + return { id: id++ }; +} diff --git a/tests/project/cases/configs/preferConstantCall.tsx b/tests/project/cases/configs/preferConstantCall.tsx new file mode 100644 index 0000000..357de6a --- /dev/null +++ b/tests/project/cases/configs/preferConstantCall.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; +import { getId } from '../common'; + +export const Comp: React.FC = () => { + const value = getId(); + /*[a]*/ const onClick = () => { + console.log(value.id); + }; /*[b]*/ + + return
Foo
; +}; diff --git a/tests/project/cases/configs/perferFullAccess.tsx b/tests/project/cases/configs/preferFullAccess.tsx similarity index 100% rename from tests/project/cases/configs/perferFullAccess.tsx rename to tests/project/cases/configs/preferFullAccess.tsx diff --git a/tests/project/cases/configs/preferImmutableCall1.tsx b/tests/project/cases/configs/preferImmutableCall1.tsx index e368d2f..4e856ee 100644 --- a/tests/project/cases/configs/preferImmutableCall1.tsx +++ b/tests/project/cases/configs/preferImmutableCall1.tsx @@ -1,14 +1,14 @@ -import * as React from 'react'; - -interface IProps { - value: number; -} - -export const Comp: React.FC = props => { - const getValue = (v: number) => props.value + v; - /*[a]*/ const onClick = () => { - console.log(getValue(props.value)); - }; /*[b]*/ - - return
Foo
; -}; +import * as React from 'react'; + +interface IProps { + value: number; +} + +export const Comp: React.FC = props => { + const getValue = (v: number) => props.value + v; + /*[a]*/ const onClick = () => { + console.log(getValue(props.value)); + }; /*[b]*/ + + return
Foo
; +}; diff --git a/tests/project/cases/configs/preferImmutableCall2.tsx b/tests/project/cases/configs/preferImmutableCall2.tsx index 24baaa1..3a189da 100644 --- a/tests/project/cases/configs/preferImmutableCall2.tsx +++ b/tests/project/cases/configs/preferImmutableCall2.tsx @@ -1,15 +1,15 @@ -import * as React from 'react'; - -interface IProps { - value: number; -} - -export const Comp: React.FC = props => { - const getValue = (v: number) => props.value + v; - /*[a]*/ const onClick = () => { - const v = 1; - console.log(getValue(v)); - }; /*[b]*/ - - return
Foo
; -}; +import * as React from 'react'; + +interface IProps { + value: number; +} + +export const Comp: React.FC = props => { + const getValue = (v: number) => props.value + v; + /*[a]*/ const onClick = () => { + const v = 1; + console.log(getValue(v)); + }; /*[b]*/ + + return
Foo
; +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral1.tsx b/tests/project/cases/constants/shouldWorkWithLiteral1.tsx index 1708afa..eb65185 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral1.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral1.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const literal = 1; - /*[a]*/ const value = 1 + literal; /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const literal = 1; + /*[a]*/ const value = 1 + literal; /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral10.tsx b/tests/project/cases/constants/shouldWorkWithLiteral10.tsx index 80a1573..48afb18 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral10.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral10.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const literal = () => 1; - /*[a]*/ const value = 1 + literal(); /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const literal = () => 1; + /*[a]*/ const value = 1 + literal(); /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral11.tsx b/tests/project/cases/constants/shouldWorkWithLiteral11.tsx index f73eb9a..2f722af 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral11.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral11.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const literal = React.useCallback(() => ({}), []); - /*[a]*/ const value = literal(); /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const literal = React.useCallback(() => ({}), []); + /*[a]*/ const value = literal(); /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral12.tsx b/tests/project/cases/constants/shouldWorkWithLiteral12.tsx index 610af40..c62f036 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral12.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral12.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const literal = () => ({}); - /*[a]*/ const value = literal(); /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const literal = () => ({}); + /*[a]*/ const value = literal(); /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral13.tsx b/tests/project/cases/constants/shouldWorkWithLiteral13.tsx index b906054..d360417 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral13.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral13.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const literal = {}; - /*[a]*/ const value = literal; /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const literal = {}; + /*[a]*/ const value = literal; /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral2.tsx b/tests/project/cases/constants/shouldWorkWithLiteral2.tsx index 5208c50..328eddb 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral2.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral2.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const literal = 1 + 1; - /*[a]*/ const value = 1 + literal; /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const literal = 1 + 1; + /*[a]*/ const value = 1 + literal; /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral3.tsx b/tests/project/cases/constants/shouldWorkWithLiteral3.tsx index 68d655b..f3fc176 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral3.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral3.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const literal = 1 + 'str'; - /*[a]*/ const value = 1 + literal; /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const literal = 1 + 'str'; + /*[a]*/ const value = 1 + literal; /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral4.tsx b/tests/project/cases/constants/shouldWorkWithLiteral4.tsx index d27afe6..c17c3bb 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral4.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral4.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const literal = 1 + /\a/; - /*[a]*/ const value = 1 + literal; /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const literal = 1 + /\a/; + /*[a]*/ const value = 1 + literal; /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral5.tsx b/tests/project/cases/constants/shouldWorkWithLiteral5.tsx index 5806f0b..f590ed7 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral5.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral5.tsx @@ -1,13 +1,13 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - // prettier-ignore - const literal = 1 + (1); - /*[a]*/ const value = 1 + literal; /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + // prettier-ignore + const literal = 1 + (1); + /*[a]*/ const value = 1 + literal; /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral6.tsx b/tests/project/cases/constants/shouldWorkWithLiteral6.tsx index a08b65f..5d5ab27 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral6.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral6.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const literal = 1 + 1!; - /*[a]*/ const value = 1 + literal; /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const literal = 1 + 1!; + /*[a]*/ const value = 1 + literal; /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral7.tsx b/tests/project/cases/constants/shouldWorkWithLiteral7.tsx index b44351e..4163bdc 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral7.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral7.tsx @@ -1,13 +1,13 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const ll = 1; - const literal = 1 + ll; - /*[a]*/ const value = 1 + literal; /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const ll = 1; + const literal = 1 + ll; + /*[a]*/ const value = 1 + literal; /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral8.tsx b/tests/project/cases/constants/shouldWorkWithLiteral8.tsx index 5c85d0e..598750b 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral8.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral8.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const literal = `${1}, ${2}`; - /*[a]*/ const value = 1 + literal; /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const literal = `${1}, ${2}`; + /*[a]*/ const value = 1 + literal; /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/project/cases/constants/shouldWorkWithLiteral9.tsx b/tests/project/cases/constants/shouldWorkWithLiteral9.tsx index b40a651..0526ffb 100644 --- a/tests/project/cases/constants/shouldWorkWithLiteral9.tsx +++ b/tests/project/cases/constants/shouldWorkWithLiteral9.tsx @@ -1,12 +1,12 @@ -import * as React from 'react'; - -export const Comp: React.FC = () => { - const literal = 1 + -1; - /*[a]*/ const value = 1 + literal; /*[b]*/ - - return ( -
- {value} -
- ); -}; +import * as React from 'react'; + +export const Comp: React.FC = () => { + const literal = 1 + -1; + /*[a]*/ const value = 1 + literal; /*[b]*/ + + return ( +
+ {value} +
+ ); +}; diff --git a/tests/suite/cases/classic/configs.test.ts b/tests/suite/cases/classic/configs.test.ts index b4ff87d..1132025 100644 --- a/tests/suite/cases/classic/configs.test.ts +++ b/tests/suite/cases/classic/configs.test.ts @@ -6,6 +6,7 @@ import { executeAndCompareCodeActionBewteenLabel, normalizedCompare, projectFile, + runInFlagContext, wait } from '../../tesUtils'; import { wrapIntoUseCallbackActionDescription } from '../../../../src/constants'; @@ -22,103 +23,95 @@ suite('Config test', async () => { }); test('Should work with preferFullAccess', async () => { - const file = projectFile('cases/configs/perferFullAccess.tsx'); + const file = projectFile('cases/configs/preferFullAccess.tsx'); const editor = await createTestEditor(file); - assert.notStrictEqual( - vscode.workspace.getConfiguration('trht').get('preferFullAccess'), - false - ); - await vscode.workspace - .getConfiguration('trht') - .update('preferFullAccess', false); - - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseCallbackActionDescription - ); - normalizedCompare( - result, + await runInFlagContext('preferFullAccess', false, async () => { + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseCallbackActionDescription + ); + normalizedCompare( + result, + ` + const onClick = React.useCallback(() => { + console.log(value.a.b); + }, [value]); ` - const onClick = React.useCallback(() => { - console.log(value.a.b); - }, [value]); - ` - ); - await vscode.workspace - .getConfiguration('trht') - .update('preferFullAccess', true); + ); + }); }); test('Should work with preferImmutableCall', async () => { const file = projectFile('cases/configs/preferImmutableCall1.tsx'); const editor = await createTestEditor(file); - assert.notStrictEqual( - vscode.workspace - .getConfiguration('trht') - .get('preferImmutableCall'), - false - ); - await vscode.workspace - .getConfiguration('trht') - .update('preferImmutableCall', false); - - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseCallbackActionDescription - ); - normalizedCompare( - result, + await runInFlagContext('preferImmutableCall', false, async () => { + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseCallbackActionDescription + ); + normalizedCompare( + result, + ` + const onClick = React.useCallback(() => { + console.log(getValue(props.value)); + }, [getValue(props.value)]); ` - const onClick = React.useCallback(() => { - console.log(getValue(props.value)); - }, [getValue(props.value)]); - ` - ); - await vscode.workspace - .getConfiguration('trht') - .update('preferImmutableCall', true); + ); + }); }); test('Should work with preferImmutableCall - inner reference', async () => { const file = projectFile('cases/configs/preferImmutableCall2.tsx'); const editor = await createTestEditor(file); - assert.notStrictEqual( - vscode.workspace - .getConfiguration('trht') - .get('preferImmutableCall'), - false - ); - await vscode.workspace - .getConfiguration('trht') - .update('preferImmutableCall', false); + await runInFlagContext('preferImmutableCall', false, async () => { + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseCallbackActionDescription + ); + normalizedCompare( + result, + ` + const onClick = React.useCallback(() => { + const v = 1; + console.log(getValue(v)); + }, [getValue]); + ` + ); + }); + }); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseCallbackActionDescription - ); - normalizedCompare( - result, + test('Should work with preferConstantCall', async () => { + const file = projectFile('cases/configs/preferConstantCall.tsx'); + const editor = await createTestEditor(file); + + await runInFlagContext('preferConstantCall', false, async () => { + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseCallbackActionDescription + ); + normalizedCompare( + result, + ` + const onClick = React.useCallback(() => { + console.log(value.id); + }, [value.id]); ` - const onClick = React.useCallback(() => { - const v = 1; - console.log(getValue(v)); - }, [getValue]); - ` - ); - await vscode.workspace - .getConfiguration('trht') - .update('preferImmutableCall', true); + ); + }); }); }); diff --git a/tests/suite/cases/classic/constants.test.ts b/tests/suite/cases/classic/constants.test.ts index fde6a5f..d1dcdf2 100644 --- a/tests/suite/cases/classic/constants.test.ts +++ b/tests/suite/cases/classic/constants.test.ts @@ -1,242 +1,242 @@ -import * as assert from 'assert'; -import * as vscode from 'vscode'; - -import { - createTestEditor, - executeAndCompareCodeActionBewteenLabel, - executeAndCompareCodeActionInLine, - normalizedCompare, - projectFile, - wait -} from '../../tesUtils'; -import { - wrapIntoUseCallbackActionDescription, - wrapIntoUseMemoActionDescription -} from '../../../../src/constants'; - -suite('Regression test', async () => { - suiteSetup(async () => { - await wait(1000); - - await vscode.workspace - .getConfiguration('trht') - .update('preferFullAccess', true); - await vscode.workspace - .getConfiguration('trht') - .update('preferImmutableCall', true); - }); - - teardown(async () => { - await vscode.commands.executeCommand( - 'workbench.action.closeAllEditors' - ); - }); - - test('Should work with constants literal - 1', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral1.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => 1 + literal, []);' - ); - }); - - test('Should work with constants literal - 2', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral2.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => 1 + literal, []);' - ); - }); - - test('Should work with constants literal - 3', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral3.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => 1 + literal, []);' - ); - }); - - test('Should work with constants literal - 4', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral4.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => 1 + literal, []);' - ); - }); - - test('Should work with constants literal - 5', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral5.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => 1 + literal, []);' - ); - }); - - test('Should work with constants literal - 6', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral6.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => 1 + literal, []);' - ); - }); - - test('Should work with constants literal - 7', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral7.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => 1 + literal, []);' - ); - }); - - test('Should work with constants literal - 8', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral8.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => 1 + literal, []);' - ); - }); - - test('Should work with constants literal - 9', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral9.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => 1 + literal, []);' - ); - }); - - test('Should work with constants literal - 10', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral10.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => 1 + literal(), []);' - ); - }); - - test('Should work with constants literal - 11', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral11.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => literal(), []);' - ); - }); - - test('Should work with constants literal - 12', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral12.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => literal(), [literal]);' - ); - }); - - test('Should work with constants literal - 13', async () => { - const file = projectFile('cases/constants/shouldWorkWithLiteral13.tsx'); - const editor = await createTestEditor(file); - const result = await executeAndCompareCodeActionBewteenLabel( - file, - editor, - 'a', - 'b', - wrapIntoUseMemoActionDescription - ); - assert.strictEqual( - result, - 'const value = React.useMemo(() => literal, [literal]);' - ); - }); -}); +import * as assert from 'assert'; +import * as vscode from 'vscode'; + +import { + createTestEditor, + executeAndCompareCodeActionBewteenLabel, + executeAndCompareCodeActionInLine, + normalizedCompare, + projectFile, + wait +} from '../../tesUtils'; +import { + wrapIntoUseCallbackActionDescription, + wrapIntoUseMemoActionDescription +} from '../../../../src/constants'; + +suite('Regression test', async () => { + suiteSetup(async () => { + await wait(1000); + + await vscode.workspace + .getConfiguration('trht') + .update('preferFullAccess', true); + await vscode.workspace + .getConfiguration('trht') + .update('preferImmutableCall', true); + }); + + teardown(async () => { + await vscode.commands.executeCommand( + 'workbench.action.closeAllEditors' + ); + }); + + test('Should work with constants literal - 1', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral1.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => 1 + literal, []);' + ); + }); + + test('Should work with constants literal - 2', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral2.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => 1 + literal, []);' + ); + }); + + test('Should work with constants literal - 3', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral3.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => 1 + literal, []);' + ); + }); + + test('Should work with constants literal - 4', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral4.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => 1 + literal, []);' + ); + }); + + test('Should work with constants literal - 5', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral5.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => 1 + literal, []);' + ); + }); + + test('Should work with constants literal - 6', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral6.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => 1 + literal, []);' + ); + }); + + test('Should work with constants literal - 7', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral7.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => 1 + literal, []);' + ); + }); + + test('Should work with constants literal - 8', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral8.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => 1 + literal, []);' + ); + }); + + test('Should work with constants literal - 9', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral9.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => 1 + literal, []);' + ); + }); + + test('Should work with constants literal - 10', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral10.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => 1 + literal(), []);' + ); + }); + + test('Should work with constants literal - 11', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral11.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => literal(), []);' + ); + }); + + test('Should work with constants literal - 12', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral12.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => literal(), [literal]);' + ); + }); + + test('Should work with constants literal - 13', async () => { + const file = projectFile('cases/constants/shouldWorkWithLiteral13.tsx'); + const editor = await createTestEditor(file); + const result = await executeAndCompareCodeActionBewteenLabel( + file, + editor, + 'a', + 'b', + wrapIntoUseMemoActionDescription + ); + assert.strictEqual( + result, + 'const value = React.useMemo(() => literal, [literal]);' + ); + }); +}); diff --git a/tests/suite/tesUtils.ts b/tests/suite/tesUtils.ts index 2fe78c3..e00e677 100644 --- a/tests/suite/tesUtils.ts +++ b/tests/suite/tesUtils.ts @@ -382,3 +382,14 @@ export function normalizeIndent(str: string) { export function normalizedCompare(a: string, b: string) { assert.strictEqual(normalizeIndent(a), normalizeIndent(b)); } + +export async function runInFlagContext( + flag: string, + value: any, + cb: () => void | Promise +) { + const savedValue = vscode.workspace.getConfiguration('trht').get(flag); + await vscode.workspace.getConfiguration('trht').update(flag, value); + await cb(); + await vscode.workspace.getConfiguration('trht').update(flag, savedValue); +}