From 10a4d8ac90a7c8ed05fd6a70be94472d863df2ba Mon Sep 17 00:00:00 2001 From: Eric MORAND Date: Thu, 2 May 2024 13:31:14 +0200 Subject: [PATCH] Resolve JS-81 --- packages/jsts/src/rules/S100/rule.ts | 23 ++++- packages/jsts/src/rules/S101/rule.ts | 19 +++- packages/jsts/src/rules/S104/rule.ts | 24 ++++- packages/jsts/src/rules/S104/unit.test.ts | 11 ++- packages/jsts/src/rules/S1067/rule.ts | 27 ++++-- packages/jsts/src/rules/S1067/unit.test.ts | 51 +++++++---- packages/jsts/src/rules/S107/rule.ts | 23 ++++- packages/jsts/src/rules/S107/unit.test.ts | 35 ++++---- packages/jsts/src/rules/S1110/rule.ts | 2 +- packages/jsts/src/rules/S1121/rule.ts | 2 +- packages/jsts/src/rules/S117/rule.ts | 21 ++++- packages/jsts/src/rules/S1226/rule.ts | 2 +- packages/jsts/src/rules/S124/rule.ts | 12 ++- packages/jsts/src/rules/S128/rule.ts | 2 +- packages/jsts/src/rules/S134/rule.ts | 23 ++++- packages/jsts/src/rules/S134/unit.test.ts | 17 +++- packages/jsts/src/rules/S138/rule.ts | 26 ++++-- packages/jsts/src/rules/S138/unit.test.ts | 31 ++++--- packages/jsts/src/rules/S1451/rule.ts | 23 ++++- packages/jsts/src/rules/S1515/rule.ts | 10 +-- packages/jsts/src/rules/S1530/rule.ts | 2 +- packages/jsts/src/rules/S1541/rule.ts | 26 ++++-- packages/jsts/src/rules/S1541/unit.test.ts | 48 ++++++---- packages/jsts/src/rules/S1994/rule.ts | 6 +- packages/jsts/src/rules/S2004/rule.ts | 21 ++++- packages/jsts/src/rules/S2004/unit.test.ts | 2 +- packages/jsts/src/rules/S2068/rule.ts | 24 ++++- packages/jsts/src/rules/S2068/unit.test.ts | 7 +- packages/jsts/src/rules/S2310/rule.ts | 4 +- packages/jsts/src/rules/S2870/rule.ts | 6 +- packages/jsts/src/rules/S2999/rule.ts | 21 ++++- packages/jsts/src/rules/S3516/rule.ts | 2 +- packages/jsts/src/rules/S3524/rule.ts | 11 ++- packages/jsts/src/rules/S3531/rule.ts | 2 +- packages/jsts/src/rules/S3800/rule.ts | 2 +- packages/jsts/src/rules/S3801/rule.ts | 2 +- packages/jsts/src/rules/S3973/rule.ts | 2 +- packages/jsts/src/rules/S3984/rule.ts | 2 +- packages/jsts/src/rules/S4322/rule.ts | 2 +- packages/jsts/src/rules/S4328/rule.ts | 25 +++++- packages/jsts/src/rules/S4328/unit.test.ts | 18 +++- packages/jsts/src/rules/S4622/rule.ts | 23 ++++- packages/jsts/src/rules/S4622/unit.test.ts | 37 ++++---- packages/jsts/src/rules/S5332/rule.lib.ts | 6 +- packages/jsts/src/rules/S5604/rule.ts | 39 ++++++-- packages/jsts/src/rules/S5604/unit.test.ts | 33 +++---- packages/jsts/src/rules/S5693/rule.ts | 25 +++++- packages/jsts/src/rules/S5693/unit.test.ts | 7 +- packages/jsts/src/rules/S5843/rule.ts | 22 ++++- packages/jsts/src/rules/S5843/unit.test.ts | 89 ++++++++++--------- packages/jsts/src/rules/S6351/rule.ts | 2 +- packages/jsts/src/rules/S6747/rule.ts | 22 ++++- packages/jsts/src/rules/helpers/ancestor.ts | 6 +- .../rules/helpers/decorators/interceptor.ts | 2 +- packages/jsts/src/rules/helpers/express.ts | 5 +- packages/jsts/src/rules/helpers/module.ts | 5 +- packages/shared/src/types/rule.ts | 55 ++++++++++++ .../javascript/checks/ContentLengthCheck.java | 17 +++- .../CyclomaticComplexityJavaScriptCheck.java | 13 ++- .../CyclomaticComplexityTypeScriptCheck.java | 14 ++- .../checks/ExpressionComplexityCheck.java | 13 ++- .../checks/HardcodedCredentialsCheck.java | 14 ++- .../checks/ImplicitDependenciesCheck.java | 22 +++-- .../checks/IntrusivePermissionsCheck.java | 16 +++- .../javascript/checks/MaxParameterCheck.java | 13 ++- .../javascript/checks/MaxUnionSizeCheck.java | 13 ++- .../checks/NestedControlFlowDepthCheck.java | 11 ++- .../checks/NoNestedFunctionsCheck.java | 13 ++- .../checks/RegexComplexityCheck.java | 13 ++- .../checks/TooManyLinesInFileCheck.java | 13 ++- .../checks/TooManyLinesInFunctionCheck.java | 13 ++- .../checks/ContentLengthCheckTest.java | 7 +- ...clomaticComplexityJavaScriptCheckTest.java | 9 +- ...clomaticComplexityTypeScriptCheckTest.java | 9 +- .../checks/ExpressionComplexityCheckTest.java | 4 +- .../checks/HardcodedCredentialsCheckTest.java | 8 +- .../checks/ImplicitDependenciesCheckTest.java | 4 +- .../checks/IntrusivePermissionsCheckTest.java | 7 +- .../checks/MaxParameterCheckTest.java | 7 +- .../checks/MaxUnionSizeCheckTest.java | 8 +- .../NestedControlFlowDepthCheckTest.java | 8 +- .../checks/NoNestedFunctionsCheckTest.java | 39 ++++++++ .../checks/RegexComplexityCheckTest.java | 7 +- .../checks/TooManyLinesInFileCheckTest.java | 4 +- .../TooManyLinesInFunctionCheckTest.java | 6 +- 85 files changed, 1021 insertions(+), 301 deletions(-) create mode 100644 packages/shared/src/types/rule.ts create mode 100644 sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/NoNestedFunctionsCheckTest.java diff --git a/packages/jsts/src/rules/S100/rule.ts b/packages/jsts/src/rules/S100/rule.ts index c1a2b1939ef..f3ee39f2b56 100644 --- a/packages/jsts/src/rules/S100/rule.ts +++ b/packages/jsts/src/rules/S100/rule.ts @@ -22,6 +22,7 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; import { last, functionLike } from '../helpers'; +import type { RuleModule } from '../../../../shared/src/types/rule'; interface FunctionKnowledge { node: estree.Identifier; @@ -52,15 +53,31 @@ const functionExpressionVariable = [ ')', ].join(''); -export const rule: Rule.RuleModule = { +export type Options = [ + { + format: string; + }, +]; + +export const rule: RuleModule = { meta: { messages: { renameFunction: "Rename this '{{function}}' function to match the regular expression '{{format}}'.", }, + schema: [ + { + type: 'object', + properties: { + format: { + type: 'string', + }, + }, + }, + ], }, create(context: Rule.RuleContext) { - const [{ format }] = context.options; + const [{ format }] = context.options as Options; const knowledgeStack: FunctionKnowledge[] = []; return { [functionExpressionProperty]: (node: estree.Property) => { @@ -111,7 +128,7 @@ export const rule: Rule.RuleModule = { }, ReturnStatement: (node: estree.ReturnStatement) => { const knowledge = last(knowledgeStack); - const ancestors = context.getAncestors(); + const ancestors = context.sourceCode.getAncestors(node); for (let i = ancestors.length - 1; i >= 0; i--) { if (functionLike.has(ancestors[i].type)) { diff --git a/packages/jsts/src/rules/S101/rule.ts b/packages/jsts/src/rules/S101/rule.ts index 2b359b54c91..e7654b12109 100644 --- a/packages/jsts/src/rules/S101/rule.ts +++ b/packages/jsts/src/rules/S101/rule.ts @@ -22,14 +22,31 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; +import type { RuleModule } from '../../../../shared/src/types/rule'; type ClassOrInterfaceDeclaration = TSESTree.ClassDeclaration | TSESTree.TSInterfaceDeclaration; -export const rule: Rule.RuleModule = { +export type Options = [ + { + format: string; + }, +]; + +export const rule: RuleModule = { meta: { messages: { renameClass: 'Rename {{symbolType}} "{{symbol}}" to match the regular expression {{format}}.', }, + schema: [ + { + type: 'object', + properties: { + format: { + type: 'string', + }, + }, + }, + ], }, create(context: Rule.RuleContext) { return { diff --git a/packages/jsts/src/rules/S104/rule.ts b/packages/jsts/src/rules/S104/rule.ts index 695f90ad75d..8a5b925775d 100644 --- a/packages/jsts/src/rules/S104/rule.ts +++ b/packages/jsts/src/rules/S104/rule.ts @@ -22,17 +22,33 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; import { getLocsNumber, getCommentLineNumbers } from '../S138/rule'; +import type { RuleModule } from '../../../../shared/src/types/rule'; -export const rule: Rule.RuleModule = { +export type Options = [ + { + maximum: number; + }, +]; + +export const rule: RuleModule = { meta: { messages: { maxFileLine: 'This file has {{lineCount}} lines, which is greater than {{threshold}} authorized. Split it into smaller files.', }, - schema: [{ type: 'integer' }], + schema: [ + { + type: 'object', + properties: { + maximum: { + type: 'integer', + }, + }, + }, + ], }, create(context: Rule.RuleContext) { - const [threshold] = context.options; + const [{ maximum: threshold }] = context.options as Options; const sourceCode = context.sourceCode; const lines = sourceCode.lines; @@ -52,7 +68,7 @@ export const rule: Rule.RuleModule = { messageId: 'maxFileLine', data: { lineCount: lineCount.toString(), - threshold, + threshold: `${threshold}`, }, loc: { line: 0, column: 0 }, }); diff --git a/packages/jsts/src/rules/S104/unit.test.ts b/packages/jsts/src/rules/S104/unit.test.ts index 09a63c39a95..928b138c221 100644 --- a/packages/jsts/src/rules/S104/unit.test.ts +++ b/packages/jsts/src/rules/S104/unit.test.ts @@ -19,6 +19,11 @@ */ import { RuleTester } from 'eslint'; import { rule } from './'; +import type { Options } from './rule'; + +const createOptions = (maximum: number): Options => { + return [{ maximum }]; +}; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); ruleTester.run('Too many lines in file', rule, { @@ -27,7 +32,7 @@ ruleTester.run('Too many lines in file', rule, { code: `a; b; c;`, - options: [3], + options: createOptions(3), }, { code: `a; @@ -35,7 +40,7 @@ ruleTester.run('Too many lines in file', rule, { b; // comment c;`, - options: [3], + options: createOptions(3), }, ], invalid: [ @@ -46,7 +51,7 @@ ruleTester.run('Too many lines in file', rule, { c; // comment d;`, - options: [3], + options: createOptions(3), errors: [ { message: `This file has 4 lines, which is greater than 3 authorized. Split it into smaller files.`, diff --git a/packages/jsts/src/rules/S1067/rule.ts b/packages/jsts/src/rules/S1067/rule.ts index 0abdcb31999..5179ac847ab 100644 --- a/packages/jsts/src/rules/S1067/rule.ts +++ b/packages/jsts/src/rules/S1067/rule.ts @@ -24,19 +24,36 @@ import * as estree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; import { toEncodedMessage } from '../helpers'; import { SONAR_RUNTIME } from '../../linter/parameters'; +import type { RuleModule } from '../../../../shared/src/types/rule'; -export const rule: Rule.RuleModule = { +export type Options = [ + { + max: number; + }, + string?, +]; + +export const rule: RuleModule = { meta: { schema: [ - { type: 'integer' }, { + type: 'object', + properties: { + max: { + type: 'integer', + }, + }, + }, + { + type: 'string', // internal parameter for rules having secondary locations enum: [SONAR_RUNTIME], }, ], }, create(context: Rule.RuleContext) { - const [max] = context.options; + const options = context.options as Options; + const threshold = options[0].max; const statementLevel: ExpressionComplexity[] = [new ExpressionComplexity()]; return { '*': (node: estree.Node) => { @@ -56,8 +73,8 @@ export const rule: Rule.RuleModule = { expr.decrementNestedExprLevel(); if (expr.isOnFirstExprLevel()) { const operators = expr.getComplexityOperators(); - if (operators.length > max) { - reportIssue(tree, operators, max, context); + if (operators.length > threshold) { + reportIssue(tree, operators, threshold, context); } expr.resetExpressionComplexityOperators(); } diff --git a/packages/jsts/src/rules/S1067/unit.test.ts b/packages/jsts/src/rules/S1067/unit.test.ts index 52d100ae04c..dcc2271dba6 100644 --- a/packages/jsts/src/rules/S1067/unit.test.ts +++ b/packages/jsts/src/rules/S1067/unit.test.ts @@ -20,70 +20,79 @@ import { RuleTester } from 'eslint'; import { rule } from './'; import { IssueLocation, EncodedMessage } from 'eslint-plugin-sonarjs/lib/utils/locations'; +import type { Options } from './rule'; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018, sourceType: 'module', ecmaFeatures: { jsx: true } }, }); -const MAX = 3; +const options: Options = [ + { + max: 3, + }, +]; ruleTester.run('Expressions should not be too complex', rule, { valid: [ { code: `let b = 1 || 2 || 3 || 4`, - options: [MAX], + options, }, { code: `let b = 1 && 2 && 4 && 4`, - options: [MAX], + options, }, { code: `let b = 1 ? ( 2 ? ( 3 ? true : false ) : false ) : false;`, - options: [MAX], + options, }, { code: `let b = foo(1 || 2 || 3, 1 || 2 || 3);`, - options: [MAX], + options, }, { code: `let b = 1 || 2 || 3 || foo(1 || 2);`, - options: [MAX], + options, }, { code: `let b = {x: 1 || 2 || 3, y: 1 || 2 || 3};`, - options: [MAX], + options, }, { code: `let b = 1 || 2 || 3 || {x: 1 || 2};`, - options: [MAX], + options, }, { code: `let b = function () {1 || 2 || 3 || 4};`, - options: [MAX], + options, }, { code: `let b = 1 || 2 || 3 || function () {1 || 2};`, - options: [MAX], + options, }, { code: `let b = 1 || 2 || 3 || function () {1 || 2 || function () {1 || 2}};`, - options: [MAX], + options, }, { code: `let b = 1 || 2 || 3 || function f() {1 || 2 || function g() {1 || 2}};`, - options: [MAX], + options, }, { code: `let b =
{1 || 2 || 3 || 4}
;`, - options: [MAX], + options, }, { code: `let b = 1 || 2 || 3 ||
{1 || 2}
;`, - options: [MAX], + options, }, { code: `let b = 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10;`, - options: [10], + options: [ + { + threshold: 10, + }, + ], }, ], invalid: [ @@ -132,7 +141,7 @@ ruleTester.run('Expressions should not be too complex', rule, { ], }); -function invalid(code: string, max = MAX) { +function invalid(code: string, max = 3) { const issue = { complexity: 0, primaryLocation: {} as IssueLocation, @@ -169,7 +178,15 @@ function invalid(code: string, max = MAX) { } } issue.secondaryLocations.sort((a, b) => b.column - a.column); - return { code, errors: [error(issue, max)], options: [max] }; + return { + code, + errors: [error(issue, max)], + options: [ + { + max, + }, + ], + }; } function error( diff --git a/packages/jsts/src/rules/S107/rule.ts b/packages/jsts/src/rules/S107/rule.ts index a7d05d36cff..d6f448c4bcb 100644 --- a/packages/jsts/src/rules/S107/rule.ts +++ b/packages/jsts/src/rules/S107/rule.ts @@ -30,12 +30,29 @@ import { interceptReport, mergeRules, } from '../helpers'; +import type { RuleModule } from '../../../../shared/src/types/rule'; const eslintMaxParams = eslintRules['max-params']; -export const rule: Rule.RuleModule = { +export type Options = [ + { + maximumFunctionParameters: number; + }, +]; + +export const rule: RuleModule = { meta: { messages: { ...eslintMaxParams.meta?.messages }, + schema: [ + { + type: 'object', + properties: { + maximumFunctionParameters: { + type: 'integer', + }, + }, + }, + ], }, create(context: Rule.RuleContext) { /** @@ -45,7 +62,7 @@ export const rule: Rule.RuleModule = { const ruleDecoration: Rule.RuleModule = interceptReport( eslintMaxParams, function (context: Rule.RuleContext, descriptor: Rule.ReportDescriptor) { - const maxParams = context.options[0] as number; + const maxParams = (context.options as Options)[0].maximumFunctionParameters; if ('node' in descriptor) { const functionLike = descriptor.node as TSESTree.FunctionLike; if (!isException(functionLike)) { @@ -119,7 +136,7 @@ export const rule: Rule.RuleModule = { function checkFunction(node: estree.Node) { const functionLike = node as unknown as TSESTree.FunctionLike; - const maxParams = context.options[0] as number; + const maxParams = (context.options as Options)[0].maximumFunctionParameters; const numParams = functionLike.params.length; if (numParams > maxParams) { context.report({ diff --git a/packages/jsts/src/rules/S107/unit.test.ts b/packages/jsts/src/rules/S107/unit.test.ts index 23cc0676201..c44e8486176 100644 --- a/packages/jsts/src/rules/S107/unit.test.ts +++ b/packages/jsts/src/rules/S107/unit.test.ts @@ -19,36 +19,41 @@ */ import { rule } from './'; import { TypeScriptRuleTester } from '../tools'; +import type { Options } from './rule'; const MAX_PARAMS_3 = 3; const MAX_PARAMS_5 = 5; +const createOptions = (maximumFunctionParameters: number): Options => { + return [{ maximumFunctionParameters }]; +}; + const ruleTester = new TypeScriptRuleTester(); ruleTester.run(``, rule, { valid: [ { code: `function f(a, b) {}`, - options: [MAX_PARAMS_5], + options: createOptions(MAX_PARAMS_5), }, { code: `function f(a, b, c, d, e) {}`, - options: [MAX_PARAMS_5], + options: createOptions(MAX_PARAMS_5), }, { code: `function f(a: any, b: any): any;`, - options: [MAX_PARAMS_5], + options: createOptions(MAX_PARAMS_5), }, { code: `function f(a: any, b: any, c: any, d: any, e: any): any;`, - options: [MAX_PARAMS_5], + options: createOptions(MAX_PARAMS_5), }, { code: `class C { m(a: any, b: any): any; }`, - options: [MAX_PARAMS_5], + options: createOptions(MAX_PARAMS_5), }, { code: `class C { constructor(private a: any, public b: any) {} }`, - options: [MAX_PARAMS_5], + options: createOptions(MAX_PARAMS_5), }, { code: ` @@ -58,17 +63,17 @@ ruleTester.run(``, rule, { constructor(a, b, c, d, e, f) {} } `, - options: [MAX_PARAMS_3], + options: createOptions(MAX_PARAMS_3), }, { code: `class C { constructor(private a: any, b: any, c: any, d: any) {} }`, - options: [MAX_PARAMS_3], + options: createOptions(MAX_PARAMS_3), }, ], invalid: [ { code: `function f(a, b, c, d, e) {}`, - options: [MAX_PARAMS_3], + options: createOptions(MAX_PARAMS_3), errors: [ { message: "Function 'f' has too many parameters (5). Maximum allowed is 3.", @@ -81,7 +86,7 @@ ruleTester.run(``, rule, { }, { code: `function f(a: any, b: any, c: any, d: any, e: any): any;`, - options: [MAX_PARAMS_3], + options: createOptions(MAX_PARAMS_3), errors: [ { message: "Function declaration 'f' has too many parameters (5). Maximum allowed is 3.", @@ -94,7 +99,7 @@ ruleTester.run(``, rule, { }, { code: `class C { m(a: any, b: any, c: any, d: any, e: any): any; }`, - options: [MAX_PARAMS_3], + options: createOptions(MAX_PARAMS_3), errors: [ { message: "Empty function 'm' has too many parameters (5). Maximum allowed is 3.", @@ -107,7 +112,7 @@ ruleTester.run(``, rule, { }, { code: `class C { constructor(a: any, b: any, c: any, d: any, e: any); }`, - options: [MAX_PARAMS_3], + options: createOptions(MAX_PARAMS_3), errors: [ { message: @@ -121,7 +126,7 @@ ruleTester.run(``, rule, { }, { code: `class C { constructor(private a: any, b: any, c: any, d: any, e: any) {} }`, - options: [MAX_PARAMS_3], + options: createOptions(MAX_PARAMS_3), errors: [ { message: 'Constructor has too many parameters (5). Maximum allowed is 3.', @@ -156,12 +161,12 @@ ruleTester.run(``, rule, { constructor(a, b, c, d, e, f) {} } `, - options: [MAX_PARAMS_3], + options: createOptions(MAX_PARAMS_3), errors: 4, }, { code: `class C { constructor(private a: any, b: any, c: any, d: any, e: any) {} }`, - options: [MAX_PARAMS_3], + options: createOptions(MAX_PARAMS_3), errors: 1, }, ], diff --git a/packages/jsts/src/rules/S1110/rule.ts b/packages/jsts/src/rules/S1110/rule.ts index 7dc70715f3b..171c277043c 100644 --- a/packages/jsts/src/rules/S1110/rule.ts +++ b/packages/jsts/src/rules/S1110/rule.ts @@ -67,7 +67,7 @@ function checkRedundantParentheses( context: Rule.RuleContext, ) { const parenthesesPairsAroundNode = getParenthesesPairsAround(sourceCode, node, node); - const parent = getParent(context); + const parent = getParent(context, node); // Ignore parentheses pair from the parent node if (!!parent && isInParentNodeParentheses(node, parent)) { diff --git a/packages/jsts/src/rules/S1121/rule.ts b/packages/jsts/src/rules/S1121/rule.ts index 57719284c32..c53e5fe6c97 100644 --- a/packages/jsts/src/rules/S1121/rule.ts +++ b/packages/jsts/src/rules/S1121/rule.ts @@ -75,7 +75,7 @@ export const rule: Rule.RuleModule = { return { AssignmentExpression: (node: estree.Node) => { const assignment = node as estree.AssignmentExpression; - const parent = getParent(context); + const parent = getParent(context, node); if ( parent && !isAssignmentStatement(parent) && diff --git a/packages/jsts/src/rules/S117/rule.ts b/packages/jsts/src/rules/S117/rule.ts index 0f6184272f7..4548bf99463 100644 --- a/packages/jsts/src/rules/S117/rule.ts +++ b/packages/jsts/src/rules/S117/rule.ts @@ -23,17 +23,34 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; import { resolveIdentifiers } from '../helpers'; +import type { RuleModule } from '../../../../shared/src/types/rule'; interface FunctionLike { declare?: boolean; params: TSESTree.Parameter[]; } -export const rule: Rule.RuleModule = { +export type Options = [ + { + format: string; + }, +]; + +export const rule: RuleModule = { meta: { messages: { renameSymbol: `Rename this {{symbolType}} "{{symbol}}" to match the regular expression {{format}}.`, }, + schema: [ + { + type: 'object', + properties: { + format: { + type: 'string', + }, + }, + }, + ], }, create(context: Rule.RuleContext) { return { @@ -87,7 +104,7 @@ function raiseOnInvalidIdentifier( idType: string, context: Rule.RuleContext, ) { - const [{ format }] = context.options; + const [{ format }] = context.options as Options; const { name } = id; if (!name.match(format)) { context.report({ diff --git a/packages/jsts/src/rules/S1226/rule.ts b/packages/jsts/src/rules/S1226/rule.ts index 5c7c3147cf3..fd665e9fd0a 100644 --- a/packages/jsts/src/rules/S1226/rule.ts +++ b/packages/jsts/src/rules/S1226/rule.ts @@ -154,7 +154,7 @@ export const rule: Rule.RuleModule = { _toSegment: Rule.CodePathSegment, node: estree.Node, ) { - const parent = getParent(context); + const parent = getParent(context, node); if (!isForEachLoopStart(node, parent)) { return; } diff --git a/packages/jsts/src/rules/S124/rule.ts b/packages/jsts/src/rules/S124/rule.ts index 307da2317ac..cd549d5b0bc 100644 --- a/packages/jsts/src/rules/S124/rule.ts +++ b/packages/jsts/src/rules/S124/rule.ts @@ -21,8 +21,17 @@ import { Rule } from 'eslint'; import { TSESTree } from '@typescript-eslint/utils'; +import type { RuleModule } from '../../../../shared/src/types/rule'; -export const rule: Rule.RuleModule = { +export type Options = [ + { + regularExpression: string; + message: string; + flags: string; + }, +]; + +export const rule: RuleModule = { meta: { schema: [ { @@ -42,7 +51,6 @@ export const rule: Rule.RuleModule = { }, ], }, - create(context: Rule.RuleContext) { const options = context.options[0] || {}; const flags = options.flags || ''; diff --git a/packages/jsts/src/rules/S128/rule.ts b/packages/jsts/src/rules/S128/rule.ts index 20ad30c8caa..c94314ae6b7 100644 --- a/packages/jsts/src/rules/S128/rule.ts +++ b/packages/jsts/src/rules/S128/rule.ts @@ -95,7 +95,7 @@ export const rule: Rule.RuleModule = { const isReachable = currentCodePath!.currentSegments.some( s => s.reachable && !isAfterProcessExitCall(s, initialSegment), ); - const { cases } = getParent(context) as estree.SwitchStatement; + const { cases } = getParent(context, node) as estree.SwitchStatement; if ( isReachable && switchCase.consequent.length > 0 && diff --git a/packages/jsts/src/rules/S134/rule.ts b/packages/jsts/src/rules/S134/rule.ts index ac930a18899..daa21a82996 100644 --- a/packages/jsts/src/rules/S134/rule.ts +++ b/packages/jsts/src/rules/S134/rule.ts @@ -23,12 +23,27 @@ import { Rule, AST } from 'eslint'; import * as estree from 'estree'; import { last, toEncodedMessage } from '../helpers'; import { SONAR_RUNTIME } from '../../linter/parameters'; +import type { RuleModule } from '../../../../shared/src/types/rule'; -export const rule: Rule.RuleModule = { +export type Options = [ + { + maximumNestingLevel: number; + }, +]; + +export const rule: RuleModule = { meta: { schema: [ - { type: 'integer' }, { + type: 'object', + properties: { + maximumNestingLevel: { + type: 'integer', + }, + }, + }, + { + type: 'string', // internal parameter for rules having secondary locations enum: [SONAR_RUNTIME], }, @@ -37,7 +52,7 @@ export const rule: Rule.RuleModule = { create(context: Rule.RuleContext) { const sourceCode = context.sourceCode; - const [threshold] = context.options; + const [{ maximumNestingLevel: threshold }] = context.options as Options; const nodeStack: AST.Token[] = []; function push(n: AST.Token) { nodeStack.push(n); @@ -58,7 +73,7 @@ export const rule: Rule.RuleModule = { } } function isElseIf(node: estree.Node) { - const parent = last(context.getAncestors()); + const parent = last(context.sourceCode.getAncestors(node)); return ( node.type === 'IfStatement' && parent.type === 'IfStatement' && node === parent.alternate ); diff --git a/packages/jsts/src/rules/S134/unit.test.ts b/packages/jsts/src/rules/S134/unit.test.ts index 50413987297..1f9e598b073 100644 --- a/packages/jsts/src/rules/S134/unit.test.ts +++ b/packages/jsts/src/rules/S134/unit.test.ts @@ -20,11 +20,16 @@ import { RuleTester } from 'eslint'; import { rule } from './'; import { IssueLocation, EncodedMessage } from 'eslint-plugin-sonarjs/lib/utils/locations'; +import type { Options } from './rule'; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018, sourceType: 'module' } }); const THRESHOLD = 3; +const createOptions = (maximumNestingLevel: number): Options => { + return [{ maximumNestingLevel }]; +}; + ruleTester.run( 'Refactor this code to not nest more than X if/for/while/switch/try statements.', rule, @@ -39,7 +44,7 @@ ruleTester.run( } } `, - options: [THRESHOLD], + options: createOptions(THRESHOLD), }, { code: ` @@ -52,7 +57,7 @@ ruleTester.run( } } `, - options: [4], + options: createOptions(4), }, { code: ` @@ -64,7 +69,7 @@ ruleTester.run( } } `, - options: [THRESHOLD], + options: createOptions(THRESHOLD), }, ], invalid: [ @@ -136,7 +141,11 @@ function invalid(code: string, threshold = THRESHOLD) { } } - return { code, errors: [error(primaryLocation, secondaryLocations)], options: [threshold] }; + return { + code, + errors: [error(primaryLocation, secondaryLocations)], + options: createOptions(threshold), + }; } function error(primaryLocation: IssueLocation, secondaryLocations: IssueLocation[]) { diff --git a/packages/jsts/src/rules/S138/rule.ts b/packages/jsts/src/rules/S138/rule.ts index 34d0fdb4d9c..5f0c1b59448 100644 --- a/packages/jsts/src/rules/S138/rule.ts +++ b/packages/jsts/src/rules/S138/rule.ts @@ -27,6 +27,7 @@ import * as estree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; import { getMainFunctionTokenLocation } from 'eslint-plugin-sonarjs/lib/utils/locations'; import { getNodeParent, getParent, last, RuleContext } from '../helpers'; +import type { RuleModule } from '../../../../shared/src/types/rule'; interface FunctionKnowledge { node: estree.Node; @@ -35,16 +36,31 @@ interface FunctionKnowledge { returnsJSX: boolean; } -export const rule: Rule.RuleModule = { +export type Options = [ + { + maximum: number; + }, +]; + +export const rule: RuleModule = { meta: { messages: { functionMaxLine: 'This function has {{lineCount}} lines, which is greater than the {{threshold}} lines authorized. Split it into smaller functions.', }, - schema: [{ type: 'integer' }], + schema: [ + { + type: 'object', + properties: { + maximum: { + type: 'integer', + }, + }, + }, + ], }, create(context: Rule.RuleContext) { - const [threshold] = context.options; + const [{ maximum: threshold }] = context.options as Options; const sourceCode = context.sourceCode; const lines = sourceCode.lines; @@ -56,7 +72,7 @@ export const rule: Rule.RuleModule = { return { 'FunctionDeclaration, FunctionExpression, ArrowFunctionExpression': (node: estree.Node) => { functionStack.push(node); - const parent = getParent(context); + const parent = getParent(context, node); if (!node.loc || isIIFE(node, parent as estree.Node)) { return; @@ -95,7 +111,7 @@ export const rule: Rule.RuleModule = { messageId: 'functionMaxLine', data: { lineCount: lineCount.toString(), - threshold, + threshold: `${threshold}`, }, loc: getMainFunctionTokenLocation( functionLike, diff --git a/packages/jsts/src/rules/S138/unit.test.ts b/packages/jsts/src/rules/S138/unit.test.ts index 5f8236e96fd..7d52b87321c 100644 --- a/packages/jsts/src/rules/S138/unit.test.ts +++ b/packages/jsts/src/rules/S138/unit.test.ts @@ -19,18 +19,23 @@ */ import { RuleTester } from 'eslint'; import { rule } from './'; +import type { Options } from '../S104/rule'; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018, ecmaFeatures: { jsx: true } }, }); +const createOptions = (maximum: number): Options => { + return [{ maximum }]; +}; + ruleTester.run('Too many lines in functions', rule, { valid: [ { code: `function f() { console.log("a"); }`, - options: [3], + options: createOptions(3), }, { code: `function f() { @@ -39,7 +44,7 @@ ruleTester.run('Too many lines in functions', rule, { }`, - options: [3], + options: createOptions(3), }, { code: `function f() { @@ -51,13 +56,13 @@ ruleTester.run('Too many lines in functions', rule, { comment */ }`, - options: [3], + options: createOptions(3), }, { code: `function foo() { console.log("a"); // End of line comment }`, - options: [3], + options: createOptions(3), }, { code: ` @@ -67,7 +72,7 @@ ruleTester.run('Too many lines in functions', rule, { } console.log("a"); `, - options: [3], + options: createOptions(3), }, { code: `function f() { @@ -75,7 +80,7 @@ ruleTester.run('Too many lines in functions', rule, { console.log("a"); } }`, - options: [5], + options: createOptions(5), }, { code: `( @@ -85,7 +90,7 @@ function } ) ()`, //IIFE are ignored - options: [6], + options: createOptions(6), }, { // React Function Component @@ -95,7 +100,7 @@ function return

{greeting}

}`, - options: [2], + options: createOptions(2), }, { // React Function Component using function expressions and JSXFragments @@ -105,7 +110,7 @@ function return <>

{greeting}

}`, - options: [2], + options: createOptions(2), }, { // React Function Component - using arrow function @@ -115,7 +120,7 @@ function return

{greeting}

}`, - options: [2], + options: createOptions(2), }, ], invalid: [ @@ -124,7 +129,7 @@ function console.log("a"); console.log("a"); }`, - options: [3], + options: createOptions(3), errors: [ { message: `This function has 4 lines, which is greater than the 3 lines authorized. Split it into smaller functions.`, @@ -141,7 +146,7 @@ function console.log("a"); console.log("b"); }`, - options: [4], + options: createOptions(4), errors: [ { message: `This function has 5 lines, which is greater than the 4 lines authorized. Split it into smaller functions.`, @@ -166,7 +171,7 @@ function return

{greeting}

}`, - options: [2], + options: createOptions(2), errors: [ { line: 5, diff --git a/packages/jsts/src/rules/S1451/rule.ts b/packages/jsts/src/rules/S1451/rule.ts index 4c3b882a895..3bccf7641cd 100644 --- a/packages/jsts/src/rules/S1451/rule.ts +++ b/packages/jsts/src/rules/S1451/rule.ts @@ -20,6 +20,7 @@ // https://sonarsource.github.io/rspec/#/rspec/S1451/javascript import { Rule } from 'eslint'; +import type { RuleModule } from '../../../../shared/src/types/rule'; let cached: { headerFormat: string; @@ -29,11 +30,31 @@ let cached: { failedToCompile?: boolean; }; -export const rule: Rule.RuleModule = { +export type Options = [ + { + headerFormat: string; + isRegularExpression: boolean; + }, +]; + +export const rule: RuleModule = { meta: { messages: { fixHeader: 'Add or update the header of this file.', }, + schema: [ + { + type: 'object', + properties: { + headerFormat: { + type: 'string', + }, + isRegularExpression: { + type: 'boolean', + }, + }, + }, + ], }, create(context: Rule.RuleContext) { updateCache(context.options); diff --git a/packages/jsts/src/rules/S1515/rule.ts b/packages/jsts/src/rules/S1515/rule.ts index 2a6dc47fe7a..ba03638035a 100644 --- a/packages/jsts/src/rules/S1515/rule.ts +++ b/packages/jsts/src/rules/S1515/rule.ts @@ -75,14 +75,14 @@ export const rule: Rule.RuleModule = { if ( loopNode && !isIIEF(node, context) && - !isAllowedCallbacks(context) && + !isAllowedCallbacks(context, node) && context.getScope().through.some(ref => !isSafe(ref, loopNode)) ) { context.report({ message: toEncodedMessage(message, [getMainLoopToken(loopNode, context)]), loc: getMainFunctionTokenLocation( node as TSESTree.FunctionLike, - getParent(context) as TSESTree.Node, + getParent(context, node) as TSESTree.Node, context as unknown as RuleContext, ), }); @@ -93,7 +93,7 @@ export const rule: Rule.RuleModule = { }; function isIIEF(node: estree.Node, context: Rule.RuleContext) { - const parent = getParent(context); + const parent = getParent(context, node); return ( parent && ((parent.type === 'CallExpression' && parent.callee === node) || @@ -101,8 +101,8 @@ function isIIEF(node: estree.Node, context: Rule.RuleContext) { ); } -function isAllowedCallbacks(context: Rule.RuleContext) { - const parent = getParent(context); +function isAllowedCallbacks(context: Rule.RuleContext, node: estree.Node) { + const parent = getParent(context, node); if (parent && parent.type === 'CallExpression') { const callee = parent.callee; if (callee.type === 'MemberExpression') { diff --git a/packages/jsts/src/rules/S1530/rule.ts b/packages/jsts/src/rules/S1530/rule.ts index 880e178e69f..981ba2a9f4a 100644 --- a/packages/jsts/src/rules/S1530/rule.ts +++ b/packages/jsts/src/rules/S1530/rule.ts @@ -39,7 +39,7 @@ export const rule: Rule.RuleModule = { messageId: 'blockedFunction', loc: getMainFunctionTokenLocation( node as TSESTree.FunctionDeclaration, - getParent(context) as TSESTree.Node, + getParent(context, node) as TSESTree.Node, context as unknown as RuleContext, ), }); diff --git a/packages/jsts/src/rules/S1541/rule.ts b/packages/jsts/src/rules/S1541/rule.ts index 9756ea7287a..11a3b24d8fc 100644 --- a/packages/jsts/src/rules/S1541/rule.ts +++ b/packages/jsts/src/rules/S1541/rule.ts @@ -30,20 +30,36 @@ import { FunctionNodeType, isFunctionNode, getParent, RuleContext } from '../hel import { TSESTree } from '@typescript-eslint/utils'; import { SONAR_RUNTIME } from '../../linter/parameters'; import { childrenOf } from '../../linter'; +import type { RuleModule } from '../../../../shared/src/types/rule'; -export const rule: Rule.RuleModule = { +export type Options = [ + { + threshold: number; + }, + string?, +]; + +export const rule: RuleModule = { meta: { schema: [ - { type: 'integer' }, { + type: 'object', + properties: { + threshold: { + type: 'integer', + }, + }, + }, + { + type: 'string', // internal parameter for rules having secondary locations enum: [SONAR_RUNTIME], }, ], }, - create(context: Rule.RuleContext) { - const [threshold] = context.options; + const options = context.options as Options; + const threshold = options[0].threshold; let functionsWithParent: Map; let functionsDefiningModule: estree.Node[]; let functionsImmediatelyInvoked: estree.Node[]; @@ -64,7 +80,7 @@ export const rule: Rule.RuleModule = { }); }, 'FunctionDeclaration, FunctionExpression, ArrowFunctionExpression': (node: estree.Node) => - functionsWithParent.set(node, getParent(context)), + functionsWithParent.set(node, getParent(context, node)), "CallExpression[callee.type='Identifier'][callee.name='define'] FunctionExpression": ( node: estree.Node, ) => functionsDefiningModule.push(node), diff --git a/packages/jsts/src/rules/S1541/unit.test.ts b/packages/jsts/src/rules/S1541/unit.test.ts index 552ce179dfc..2cea74abb6e 100644 --- a/packages/jsts/src/rules/S1541/unit.test.ts +++ b/packages/jsts/src/rules/S1541/unit.test.ts @@ -20,10 +20,14 @@ import { RuleTester } from 'eslint'; import { rule } from './'; import { IssueLocation, EncodedMessage } from 'eslint-plugin-sonarjs/lib/utils/locations'; +import type { Options } from './rule'; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018, sourceType: 'module' } }); - -const THRESHOLD = 2; +const options: Options = [ + { + threshold: 2, + }, +]; ruleTester.run('Functions should not be too complex', rule, { valid: [ @@ -33,7 +37,7 @@ ruleTester.run('Functions should not be too complex', rule, { if (x) {} if (x) {} `, - options: [THRESHOLD], + options, }, { code: ` @@ -45,7 +49,7 @@ ruleTester.run('Functions should not be too complex', rule, { } } `, - options: [THRESHOLD], + options, }, { code: ` @@ -58,7 +62,7 @@ ruleTester.run('Functions should not be too complex', rule, { } } `, - options: [THRESHOLD], + options, }, { code: ` @@ -67,7 +71,7 @@ ruleTester.run('Functions should not be too complex', rule, { b = arr.map(s => s.length); // OK +0 for ok } `, - options: [THRESHOLD], + options, }, { code: ` @@ -76,7 +80,7 @@ ruleTester.run('Functions should not be too complex', rule, { b = () => 10; // OK +0 for ok } `, - options: [THRESHOLD], + options, }, { code: ` @@ -88,7 +92,7 @@ ruleTester.run('Functions should not be too complex', rule, { } } `, - options: [THRESHOLD], + options, }, { code: ` @@ -100,7 +104,7 @@ ruleTester.run('Functions should not be too complex', rule, { }; } `, - options: [THRESHOLD], + options, }, { code: ` @@ -111,7 +115,7 @@ ruleTester.run('Functions should not be too complex', rule, { } } `, - options: [THRESHOLD], + options, }, { code: ` @@ -121,7 +125,7 @@ ruleTester.run('Functions should not be too complex', rule, { if (x) {} })(34); `, - options: [THRESHOLD], + options, }, { code: ` @@ -129,7 +133,7 @@ ruleTester.run('Functions should not be too complex', rule, { var a = true && false && true; }(); `, - options: [THRESHOLD], + options, }, { code: ` @@ -137,7 +141,7 @@ ruleTester.run('Functions should not be too complex', rule, { var a = true && false && true; })(); `, - options: [THRESHOLD], + options, }, { code: ` @@ -145,7 +149,7 @@ ruleTester.run('Functions should not be too complex', rule, { var a = true && false && true; }); `, - options: [THRESHOLD], + options, }, { code: ` @@ -153,7 +157,7 @@ ruleTester.run('Functions should not be too complex', rule, { var a = true && false && true; }); `, - options: [THRESHOLD], + options, }, // TODO not supported yet // { @@ -174,7 +178,7 @@ ruleTester.run('Functions should not be too complex', rule, { // var a = true && false && true; // }); // `, - // options: [THRESHOLD], + // options, // }, ], invalid: [ @@ -378,7 +382,7 @@ ruleTester.run('Functions should not be too complex', rule, { ], }); -function invalid(code: string, threshold = THRESHOLD) { +function invalid(code: string, threshold = 2) { const issue = { complexity: 0, primaryLocation: {} as IssueLocation, @@ -406,7 +410,15 @@ function invalid(code: string, threshold = THRESHOLD) { } } - return { code, errors: [error(issue, threshold)], options: [threshold] }; + return { + code, + errors: [error(issue, threshold)], + options: [ + { + threshold, + }, + ], + }; } function error( diff --git a/packages/jsts/src/rules/S1994/rule.ts b/packages/jsts/src/rules/S1994/rule.ts index 74b15b92cfa..08f175c4dc5 100644 --- a/packages/jsts/src/rules/S1994/rule.ts +++ b/packages/jsts/src/rules/S1994/rule.ts @@ -132,7 +132,7 @@ export const rule: Rule.RuleModule = { 'ForStatement Identifier': (node: estree.Node) => { if (isInsideTest(node)) { - const parent = getParent(context)!; + const parent = getParent(context, node)!; if (parent.type !== 'MemberExpression' || parent.computed || parent.object === node) { peekFor().testedExpressions.push(node); } @@ -142,8 +142,8 @@ export const rule: Rule.RuleModule = { 'ForStatement MemberExpression': (node: estree.Node) => { if ( isInsideTest(node) && - getParent(context)!.type !== 'MemberExpression' && - getParent(context)!.type !== 'CallExpression' + getParent(context, node)!.type !== 'MemberExpression' && + getParent(context, node)!.type !== 'CallExpression' ) { peekFor().testedExpressions.push(node); } diff --git a/packages/jsts/src/rules/S2004/rule.ts b/packages/jsts/src/rules/S2004/rule.ts index 525f36f7f88..7a045d6791e 100644 --- a/packages/jsts/src/rules/S2004/rule.ts +++ b/packages/jsts/src/rules/S2004/rule.ts @@ -25,21 +25,36 @@ import { TSESTree } from '@typescript-eslint/utils'; import { getMainFunctionTokenLocation } from 'eslint-plugin-sonarjs/lib/utils/locations'; import { SONAR_RUNTIME } from '../../linter/parameters'; import { RuleContext, toEncodedMessage } from '../helpers'; +import type { RuleModule } from '../../../../shared/src/types/rule'; const DEFAULT_THRESHOLD = 4; -export const rule: Rule.RuleModule = { +export type Options = [ + { + threshold: number; + }, +]; + +export const rule: RuleModule = { meta: { schema: [ - { type: 'integer' }, { + type: 'object', + properties: { + threshold: { + type: 'integer', + }, + }, + }, + { + type: 'string', // internal parameter for rules having secondary locations enum: [SONAR_RUNTIME], }, ], }, create(context: Rule.RuleContext) { - const max = context.options[0] || DEFAULT_THRESHOLD; + const max = (context.options as Options)[0]?.threshold || DEFAULT_THRESHOLD; const nestedStack: TSESTree.FunctionLike[] = []; return { ':function'(node: estree.Node) { diff --git a/packages/jsts/src/rules/S2004/unit.test.ts b/packages/jsts/src/rules/S2004/unit.test.ts index 07cf737a0b7..b8b2ff713ee 100644 --- a/packages/jsts/src/rules/S2004/unit.test.ts +++ b/packages/jsts/src/rules/S2004/unit.test.ts @@ -44,7 +44,7 @@ ruleTester.run('Functions should not be nested too deeply', rule, { } } }`, - options: [2], + options: [{ threshold: 2 }], errors: 1, }, ], diff --git a/packages/jsts/src/rules/S2068/rule.ts b/packages/jsts/src/rules/S2068/rule.ts index b2e4ca22f53..a1ca8daac11 100644 --- a/packages/jsts/src/rules/S2068/rule.ts +++ b/packages/jsts/src/rules/S2068/rule.ts @@ -23,12 +23,32 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; import { isStringLiteral } from '../helpers'; import path from 'path'; +import type { RuleModule } from '../../../../shared/src/types/rule'; -export const rule: Rule.RuleModule = { +export type Options = [ + { + credentialWords: Array; + }, +]; + +export const rule: RuleModule = { meta: { messages: { reviewCredential: 'Review this potentially hardcoded credential.', }, + schema: [ + { + type: 'object', + properties: { + credentialWords: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + }, + ], }, create(context: Rule.RuleContext) { const dir = path.dirname(context.physicalFilename); @@ -37,7 +57,7 @@ export const rule: Rule.RuleModule = { return {}; } - const variableNames = context.options; + const [{ credentialWords: variableNames }] = context.options as Options; const literalRegExp = variableNames.map(name => new RegExp(`${name}=.+`)); return { VariableDeclarator: (node: estree.Node) => { diff --git a/packages/jsts/src/rules/S2068/unit.test.ts b/packages/jsts/src/rules/S2068/unit.test.ts index 6ffb6f2791b..45021b816f1 100644 --- a/packages/jsts/src/rules/S2068/unit.test.ts +++ b/packages/jsts/src/rules/S2068/unit.test.ts @@ -20,13 +20,14 @@ import { RuleTester } from 'eslint'; import { rule } from './'; import path from 'path'; +import type { Options } from './rule'; const ruleTester = new RuleTester({ parser: require.resolve('@typescript-eslint/parser'), parserOptions: { ecmaVersion: 2018, sourceType: 'module' }, }); -const options = ['password', 'pwd', 'passwd']; +const options: Options = [{ credentialWords: ['password', 'pwd', 'passwd'] }]; ruleTester.run('Hardcoded credentials should be avoided', rule, { valid: [ @@ -79,12 +80,12 @@ ruleTester.run('Hardcoded credentials should be avoided', rule, { }, { code: `let secret = "foo"`, - options: ['secret'], + options: [{ credentialWords: ['secret'] }], errors: 1, }, { code: `let url = "https://example.com?token=hl2OAIXXZ60";`, - options: ['token'], + options: [{ credentialWords: ['token'] }], errors: 1, }, { diff --git a/packages/jsts/src/rules/S2310/rule.ts b/packages/jsts/src/rules/S2310/rule.ts index 5b1de3b8bf1..d0a41624aa9 100644 --- a/packages/jsts/src/rules/S2310/rule.ts +++ b/packages/jsts/src/rules/S2310/rule.ts @@ -67,13 +67,13 @@ export const rule: Rule.RuleModule = { return { 'ForStatement > BlockStatement': (node: estree.Node) => { - const forLoop = getParent(context) as estree.ForStatement; + const forLoop = getParent(context, node) as estree.ForStatement; if (forLoop.update) { checkLoop(forLoop.update, collectCountersFor, node); } }, 'ForInStatement > BlockStatement, ForOfStatement > BlockStatement': (node: estree.Node) => { - const { left } = getParent(context) as estree.ForOfStatement | estree.ForInStatement; + const { left } = getParent(context, node) as estree.ForOfStatement | estree.ForInStatement; checkLoop(left, collectCountersForX, node); }, }; diff --git a/packages/jsts/src/rules/S2870/rule.ts b/packages/jsts/src/rules/S2870/rule.ts index ccc448db776..edf3709fcf6 100644 --- a/packages/jsts/src/rules/S2870/rule.ts +++ b/packages/jsts/src/rules/S2870/rule.ts @@ -40,7 +40,7 @@ export const rule: Rule.RuleModule = { const member = node as estree.MemberExpression; const object = member.object; if (isArray(object, services)) { - raiseIssue(context); + raiseIssue(context, node); } }, }; @@ -49,8 +49,8 @@ export const rule: Rule.RuleModule = { }, }; -function raiseIssue(context: Rule.RuleContext): void { - const deleteKeyword = context.sourceCode.getFirstToken(getParent(context)!); +function raiseIssue(context: Rule.RuleContext, node: estree.Node): void { + const deleteKeyword = context.sourceCode.getFirstToken(getParent(context, node)!); context.report({ messageId: 'removeDelete', loc: deleteKeyword!.loc, diff --git a/packages/jsts/src/rules/S2999/rule.ts b/packages/jsts/src/rules/S2999/rule.ts index de7eb324957..6df1b00b5ac 100644 --- a/packages/jsts/src/rules/S2999/rule.ts +++ b/packages/jsts/src/rules/S2999/rule.ts @@ -29,19 +29,34 @@ import { toEncodedMessage, } from '../helpers'; import { SONAR_RUNTIME } from '../../linter/parameters'; +import type { RuleModule } from '../../../../shared/src/types/rule'; -export const rule: Rule.RuleModule = { +export type Options = [ + { + considerJSDoc: boolean; + }, +]; + +export const rule: RuleModule = { meta: { schema: [ - { type: 'object' }, { + type: 'object', + properties: { + considerJSDoc: { + type: 'boolean', + }, + }, + }, + { + type: 'string', // internal parameter for rules having secondary locations enum: [SONAR_RUNTIME], }, ], }, create(context: Rule.RuleContext) { - const { considerJSDoc } = context.options[0]; + const { considerJSDoc } = (context.options as Options)[0]; const services = context.sourceCode.parserServices; if (!isRequiredParserServices(services)) { return {}; diff --git a/packages/jsts/src/rules/S3516/rule.ts b/packages/jsts/src/rules/S3516/rule.ts index 001209759ea..3cb701e4653 100644 --- a/packages/jsts/src/rules/S3516/rule.ts +++ b/packages/jsts/src/rules/S3516/rule.ts @@ -82,7 +82,7 @@ export const rule: Rule.RuleModule = { message, loc: getMainFunctionTokenLocation( node as TSESTree.FunctionLike, - getParent(context) as TSESTree.Node, + getParent(context, node) as TSESTree.Node, context as unknown as RuleContext, ), }); diff --git a/packages/jsts/src/rules/S3524/rule.ts b/packages/jsts/src/rules/S3524/rule.ts index c63b3825c87..868537f4917 100644 --- a/packages/jsts/src/rules/S3524/rule.ts +++ b/packages/jsts/src/rules/S3524/rule.ts @@ -22,13 +22,21 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; +import type { RuleModule } from '../../../../shared/src/types/rule'; const MESSAGE_ADD_PARAMETER = 'Add parentheses around the parameter of this arrow function.'; const MESSAGE_REMOVE_PARAMETER = 'Remove parentheses around the parameter of this arrow function.'; const MESSAGE_ADD_BODY = 'Add curly braces and "return" to this arrow function body.'; const MESSAGE_REMOVE_BODY = 'Remove curly braces and "return" from this arrow function body.'; -export const rule: Rule.RuleModule = { +export type Options = [ + { + requireParameterParentheses: boolean; + requireBodyBraces: boolean; + }, +]; + +export const rule: RuleModule = { meta: { schema: [ { @@ -45,7 +53,6 @@ export const rule: Rule.RuleModule = { }, ], }, - create(context: Rule.RuleContext) { const options = context.options[0] || {}; const requireParameterParentheses = !!options.requireParameterParentheses; diff --git a/packages/jsts/src/rules/S3531/rule.ts b/packages/jsts/src/rules/S3531/rule.ts index caca8640edd..e12b80859af 100644 --- a/packages/jsts/src/rules/S3531/rule.ts +++ b/packages/jsts/src/rules/S3531/rule.ts @@ -46,7 +46,7 @@ export const rule: Rule.RuleModule = { messageId: 'addYield', loc: getMainFunctionTokenLocation( functionNode as TSESTree.FunctionLike, - getParent(context) as TSESTree.Node, + getParent(context, node) as TSESTree.Node, context as unknown as RuleContext, ), }); diff --git a/packages/jsts/src/rules/S3800/rule.ts b/packages/jsts/src/rules/S3800/rule.ts index c5063734bc4..54a38edf5c0 100644 --- a/packages/jsts/src/rules/S3800/rule.ts +++ b/packages/jsts/src/rules/S3800/rule.ts @@ -102,7 +102,7 @@ export const rule: Rule.RuleModule = { ), loc: getMainFunctionTokenLocation( node as TSESTree.FunctionLike, - getParent(context) as TSESTree.Node, + getParent(context, node) as TSESTree.Node, context as unknown as RuleContext, ), }); diff --git a/packages/jsts/src/rules/S3801/rule.ts b/packages/jsts/src/rules/S3801/rule.ts index 6e38fcd3e9f..cc28111a46e 100644 --- a/packages/jsts/src/rules/S3801/rule.ts +++ b/packages/jsts/src/rules/S3801/rule.ts @@ -88,7 +88,7 @@ export const rule: Rule.RuleModule = { message, loc: getMainFunctionTokenLocation( node as TSESTree.FunctionLike, - getParent(context) as TSESTree.Node, + getParent(context, node as estree.Node) as TSESTree.Node, context as unknown as RuleContext, ), }); diff --git a/packages/jsts/src/rules/S3973/rule.ts b/packages/jsts/src/rules/S3973/rule.ts index db678fbd470..6e4e906f795 100644 --- a/packages/jsts/src/rules/S3973/rule.ts +++ b/packages/jsts/src/rules/S3973/rule.ts @@ -40,7 +40,7 @@ export const rule: Rule.RuleModule = { return { IfStatement: (node: estree.Node) => { const ifStatement = node as estree.IfStatement; - const parent = getParent(context); + const parent = getParent(context, node); if (parent && parent.type !== 'IfStatement') { const firstToken = sourceCode.getFirstToken(node); checkIndentation(firstToken, ifStatement.consequent, context); diff --git a/packages/jsts/src/rules/S3984/rule.ts b/packages/jsts/src/rules/S3984/rule.ts index bfacb489d73..299fdbb58f1 100644 --- a/packages/jsts/src/rules/S3984/rule.ts +++ b/packages/jsts/src/rules/S3984/rule.ts @@ -47,7 +47,7 @@ export const rule: Rule.RuleModule = { suggest: [ { messageId: 'suggestThrowError', - fix: fixer => fixer.insertTextBefore(getParent(context)!, 'throw '), + fix: fixer => fixer.insertTextBefore(getParent(context, node)!, 'throw '), }, ], }); diff --git a/packages/jsts/src/rules/S4322/rule.ts b/packages/jsts/src/rules/S4322/rule.ts index b03736d9231..64953c48b64 100644 --- a/packages/jsts/src/rules/S4322/rule.ts +++ b/packages/jsts/src/rules/S4322/rule.ts @@ -122,7 +122,7 @@ function checkCastedType( }, loc: getMainFunctionTokenLocation( node as TSESTree.FunctionLike, - getParent(context) as TSESTree.Node, + getParent(context, node as estree.Node) as TSESTree.Node, context as unknown as RuleContext, ), suggest, diff --git a/packages/jsts/src/rules/S4328/rule.ts b/packages/jsts/src/rules/S4328/rule.ts index a87c06adbc6..d0f98efd82d 100644 --- a/packages/jsts/src/rules/S4328/rule.ts +++ b/packages/jsts/src/rules/S4328/rule.ts @@ -27,15 +27,36 @@ import * as fs from 'fs'; import * as ts from 'typescript'; import { RequiredParserServices } from '../helpers'; import { getDependencies } from '@sonar/jsts'; +import type { RuleModule } from '../../../../shared/src/types/rule'; -export const rule: Rule.RuleModule = { +export type Options = [ + { + whitelist: Array; + }, +]; + +export const rule: RuleModule = { meta: { messages: { removeOrAddDependency: 'Either remove this import or add it as a dependency.', }, + schema: [ + { + type: 'object', + properties: { + whitelist: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + }, + ], }, create(context: Rule.RuleContext) { - const whitelist = context.options; + const options = context.options as Options; + const whitelist = options.length > 0 ? options[0].whitelist : []; const dependencies = getDependencies(context.filename); const aliasedPathsMappingPatterns = extractPathMappingPatterns( context.sourceCode.parserServices, diff --git a/packages/jsts/src/rules/S4328/unit.test.ts b/packages/jsts/src/rules/S4328/unit.test.ts index 9656bdb64b1..32c4cacf0af 100644 --- a/packages/jsts/src/rules/S4328/unit.test.ts +++ b/packages/jsts/src/rules/S4328/unit.test.ts @@ -21,6 +21,7 @@ import { RuleTester } from 'eslint'; import { rule } from './'; import path from 'path'; import { clearPackageJsons, loadPackageJsons } from '@sonar/jsts'; +import type { Options } from './rule'; //reset and search package.json files in rule dir clearPackageJsons(); @@ -28,7 +29,11 @@ loadPackageJsons(__dirname, []); const fixtures = path.join(__dirname, 'fixtures'); const filename = path.join(fixtures, 'package-json-project/file.js'); -const options = []; +const options: Options = [ + { + whitelist: [], + }, +]; const tsParserPath = require.resolve('@typescript-eslint/parser'); const ruleTester = new RuleTester({ parser: tsParserPath, @@ -70,12 +75,12 @@ ruleTester.run('Dependencies should be explicit', rule, { { code: `import "whitelist";`, filename, - options: ['whitelist'], + options: [{ whitelist: ['whitelist'] }], }, { code: `import "@whitelist/dependency";`, filename, - options: ['@whitelist/dependency'], + options: [{ whitelist: ['@whitelist/dependency'] }], }, { code: `import "./relative";`, @@ -190,6 +195,7 @@ ruleTesterNestedPackage.run('all levels of package.json should be considered', r import { f as f2 } from 'local-dependency'; `, filename: filenameNestedPackage, + options, }, ], invalid: [ @@ -198,6 +204,7 @@ ruleTesterNestedPackage.run('all levels of package.json should be considered', r import { f as f1 } from 'nonexistent'; `, filename: filenameNestedPackage, + options, errors: 1, }, ], @@ -230,6 +237,7 @@ ruleTesterForPathMappings.run('Path aliases should be exempt', rule, { import { f as f9 } from 'dependency-in-package-json'; `, filename: filenameForFileWithPathMappings, + options, }, ], invalid: [ @@ -247,6 +255,7 @@ ruleTesterForPathMappings.run('Path aliases should be exempt', rule, { import { f as fA } from 'dependency-not-in-package-json'; `, filename: filenameForFileWithPathMappings, + options, errors: 10, }, ], @@ -275,6 +284,7 @@ ruleTesterForBaseUrl.run('Imports based on baseUrl should be accepted', rule, { import { f as f4 } from 'dir/b'; `, filename: filenameForBaseUrl, + options, }, ], invalid: [ @@ -284,6 +294,7 @@ ruleTesterForBaseUrl.run('Imports based on baseUrl should be accepted', rule, { import { f as f1 } from 'dir/nonexistent'; `, filename: filenameForBaseUrl, + options, errors: 2, }, ], @@ -313,6 +324,7 @@ ruleTesterForCatchAllExample.run( let f = require("this/might/be/generated").f; `, filename: filenameCatchAllExample, + options, }, ], invalid: [], diff --git a/packages/jsts/src/rules/S4622/rule.ts b/packages/jsts/src/rules/S4622/rule.ts index 4aa4acb206e..8648f9a8b4d 100644 --- a/packages/jsts/src/rules/S4622/rule.ts +++ b/packages/jsts/src/rules/S4622/rule.ts @@ -23,12 +23,29 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; import { UTILITY_TYPES, isIdentifier } from '../helpers'; +import type { RuleModule } from '../../../../shared/src/types/rule'; -export const rule: Rule.RuleModule = { +export type Options = [ + { + threshold: number; + }, +]; + +export const rule: RuleModule = { meta: { messages: { refactorUnion: 'Refactor this union type to have less than {{threshold}} elements.', }, + schema: [ + { + type: 'object', + properties: { + threshold: { + type: 'integer', + }, + }, + }, + ], }, create(context: Rule.RuleContext) { return { @@ -37,12 +54,12 @@ export const rule: Rule.RuleModule = { if (isUsedWithUtilityType(union)) { return; } - const [threshold] = context.options; + const [{ threshold }] = context.options as Options; if (union.types.length > threshold && !isFromTypeStatement(union)) { context.report({ messageId: 'refactorUnion', data: { - threshold, + threshold: `${threshold}`, }, node, }); diff --git a/packages/jsts/src/rules/S4622/unit.test.ts b/packages/jsts/src/rules/S4622/unit.test.ts index 9dc835661ea..426538319fc 100644 --- a/packages/jsts/src/rules/S4622/unit.test.ts +++ b/packages/jsts/src/rules/S4622/unit.test.ts @@ -19,6 +19,7 @@ */ import { RuleTester } from 'eslint'; import { rule } from './'; +import type { Options } from './rule'; const ruleTester = new RuleTester({ parser: require.resolve('@typescript-eslint/parser'), @@ -28,47 +29,51 @@ const ruleTester = new RuleTester({ const DEFAULT_THRESHOLD = 3; const CUSTOM_THRESHOLD = 4; +const createOptions = (threshold: number): Options => { + return [{ threshold }]; +}; + ruleTester.run('Union types should not have too many elements', rule, { valid: [ { code: `let smallUnionType: number | boolean | string;`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), }, { code: `let smallUnionType: number | boolean | string | any[];`, - options: [CUSTOM_THRESHOLD], + options: createOptions(CUSTOM_THRESHOLD), }, { code: `function smallUnionType(a: number | boolean) {}`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), }, { code: `type T = A | B | C | D;`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), }, { code: ` type T = A | B | C | D; function okFn(a: T) {}`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), }, { code: ` type T = A | B | C | D; let okVarA : T;`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), }, { code: ` type T = A | B | C | D; let okFunctionType: (param: any) => T`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), }, { code: ` type T = A | B | C | D; let okTupleType: [string, T];`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), }, { code: ` @@ -82,13 +87,13 @@ ruleTester.run('Union types should not have too many elements', rule, { }; type Bar = Pick; `, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), }, ], invalid: [ { code: `let nokVarA: A | B | C | D`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), errors: [ { message: `Refactor this union type to have less than ${DEFAULT_THRESHOLD} elements.`, @@ -101,7 +106,7 @@ ruleTester.run('Union types should not have too many elements', rule, { }, { code: `let nokVarA: A | B | C | D | E`, - options: [CUSTOM_THRESHOLD], + options: createOptions(CUSTOM_THRESHOLD), errors: [ { message: `Refactor this union type to have less than ${CUSTOM_THRESHOLD} elements.`, @@ -114,7 +119,7 @@ ruleTester.run('Union types should not have too many elements', rule, { }, { code: `function nokFn(a: A | B | C | D) {}`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), errors: [ { message: `Refactor this union type to have less than ${DEFAULT_THRESHOLD} elements.`, @@ -123,7 +128,7 @@ ruleTester.run('Union types should not have too many elements', rule, { }, { code: `let nokFunctionType: (param: any) => A | B | C | D`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), errors: [ { message: `Refactor this union type to have less than ${DEFAULT_THRESHOLD} elements.`, @@ -132,7 +137,7 @@ ruleTester.run('Union types should not have too many elements', rule, { }, { code: `let nokTupleType : [string, A | B | C | D];`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), errors: [ { message: `Refactor this union type to have less than ${DEFAULT_THRESHOLD} elements.`, @@ -143,7 +148,7 @@ ruleTester.run('Union types should not have too many elements', rule, { code: `interface nokInterfaceDeclaration { prop: A | B | C | D; }`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), errors: [ { message: `Refactor this union type to have less than ${DEFAULT_THRESHOLD} elements.`, @@ -152,7 +157,7 @@ ruleTester.run('Union types should not have too many elements', rule, { }, { code: `type U = (A | B | C | D) & E;`, - options: [DEFAULT_THRESHOLD], + options: createOptions(DEFAULT_THRESHOLD), errors: [ { message: `Refactor this union type to have less than ${DEFAULT_THRESHOLD} elements.`, diff --git a/packages/jsts/src/rules/S5332/rule.lib.ts b/packages/jsts/src/rules/S5332/rule.lib.ts index 98e95dba6d0..f7433a402dd 100644 --- a/packages/jsts/src/rules/S5332/rule.lib.ts +++ b/packages/jsts/src/rules/S5332/rule.lib.ts @@ -160,9 +160,9 @@ export const rule: Rule.RuleModule = { } } - function isExceptionUrl(value: string) { + function isExceptionUrl(value: string, node: estree.Node) { if (INSECURE_PROTOCOLS.includes(value)) { - const parent = getParent(context); + const parent = getParent(context, node); return !(parent?.type === 'BinaryExpression' && parent.operator === '+'); } return hasExceptionHost(value); @@ -192,7 +192,7 @@ export const rule: Rule.RuleModule = { if (typeof literal.value === 'string') { const value = literal.value.trim().toLocaleLowerCase(); const insecure = INSECURE_PROTOCOLS.find(protocol => value.startsWith(protocol)); - if (insecure && !isExceptionUrl(value)) { + if (insecure && !isExceptionUrl(value, node)) { const protocol = insecure.substring(0, insecure.indexOf(':')); context.report({ ...getMessageAndData(protocol), diff --git a/packages/jsts/src/rules/S5604/rule.ts b/packages/jsts/src/rules/S5604/rule.ts index b4e69a14880..f3a7bdbb2d5 100644 --- a/packages/jsts/src/rules/S5604/rule.ts +++ b/packages/jsts/src/rules/S5604/rule.ts @@ -22,14 +22,34 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; import { isIdentifier, isMemberExpression, getValueOfExpression } from '../helpers'; +import type { RuleModule } from '../../../../shared/src/types/rule'; const permissions = ['geolocation', 'camera', 'microphone', 'notifications', 'persistent-storage']; -export const rule: Rule.RuleModule = { +export type Options = [ + { + permissions: Array; + }, +]; + +export const rule: RuleModule = { meta: { messages: { checkPermission: 'Make sure the use of the {{feature}} is necessary.', }, + schema: [ + { + type: 'object', + properties: { + permissions: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + }, + ], }, create(context: Rule.RuleContext) { return { @@ -44,7 +64,7 @@ export const rule: Rule.RuleModule = { return; } if ( - context.options.includes('geolocation') && + (context.options as Options)[0].permissions.includes('geolocation') && isNavigatorMemberExpression(callee, 'geolocation', 'watchPosition', 'getCurrentPosition') ) { context.report({ @@ -65,7 +85,7 @@ export const rule: Rule.RuleModule = { return; } if ( - context.options.includes('notifications') && + (context.options as Options)[0].permissions.includes('notifications') && isMemberExpression(callee, 'Notification', 'requestPermission') ) { context.report({ @@ -78,7 +98,7 @@ export const rule: Rule.RuleModule = { return; } if ( - context.options.includes('persistent-storage') && + (context.options as Options)[0].permissions.includes('persistent-storage') && isMemberExpression(callee.object, 'navigator', 'storage') ) { context.report({ @@ -92,7 +112,10 @@ export const rule: Rule.RuleModule = { }, NewExpression(node: estree.Node) { const { callee } = node as estree.NewExpression; - if (context.options.includes('notifications') && isIdentifier(callee, 'Notification')) { + if ( + (context.options as Options)[0].permissions.includes('notifications') && + isIdentifier(callee, 'Notification') + ) { context.report({ messageId: 'checkPermission', data: { @@ -114,8 +137,8 @@ function checkForCameraAndMicrophonePermissions( if (!firstArg) { return; } - const shouldCheckAudio = context.options.includes('microphone'); - const shouldCheckVideo = context.options.includes('camera'); + const shouldCheckAudio = (context.options as Options)[0].permissions.includes('microphone'); + const shouldCheckVideo = (context.options as Options)[0].permissions.includes('camera'); if (!shouldCheckAudio && !shouldCheckVideo) { return; } @@ -191,7 +214,7 @@ function hasNamePropertyWithPermission( value && typeof value.value === 'string' && permissions.includes(value.value) && - context.options.includes(value.value) + (context.options as Options)[0].permissions.includes(value.value) ); } return false; diff --git a/packages/jsts/src/rules/S5604/unit.test.ts b/packages/jsts/src/rules/S5604/unit.test.ts index d458527bb77..822336c4197 100644 --- a/packages/jsts/src/rules/S5604/unit.test.ts +++ b/packages/jsts/src/rules/S5604/unit.test.ts @@ -19,13 +19,14 @@ */ import { RuleTester } from 'eslint'; import { rule } from './'; +import type { Options } from './rule'; const ruleTester = new RuleTester({ parser: require.resolve('@typescript-eslint/parser'), parserOptions: { ecmaVersion: 2018, sourceType: 'module' }, }); -const defaultOptions = ['geolocation']; +const defaultOptions: Options = [{ permissions: ['geolocation'] }]; ruleTester.run('', rule, { valid: [ @@ -51,11 +52,11 @@ ruleTester.run('', rule, { }, { code: `navigator.permissions.query({name:"foo"})`, - options: ['foo'], + options: [{ permissions: ['foo'] }], }, { code: `navigator.geolocation.watchPosition()`, - options: ['camera'], + options: [{ permissions: ['camera'] }], }, { code: `navigator.mediaDevices.getUserMedia()`, @@ -63,19 +64,19 @@ ruleTester.run('', rule, { }, { code: `navigator.mediaDevices.getUserMedia({ audio: true })`, - options: [], + options: [{ permissions: [] }], }, { code: `param => navigator.mediaDevices.getUserMedia(param)`, - options: ['camera'], + options: [{ permissions: ['camera'] }], }, { code: `navigator.mediaDevices.getUserMedia({ other: true })`, - options: ['camera'], + options: [{ permissions: ['camera'] }], }, { code: `param => navigator.mediaDevices.getUserMedia({ ...param })`, - options: ['camera'], + options: [{ permissions: ['camera'] }], }, { code: `Notification.requestPermission()`, @@ -104,12 +105,12 @@ ruleTester.run('', rule, { }, { code: `navigator.permissions.query({name: "camera"})`, - options: ['camera'], + options: [{ permissions: ['camera'] }], errors: 1, }, { code: `navigator.permissions.query({name: "microphone"})`, - options: ['microphone'], + options: [{ permissions: ['microphone'] }], errors: [ { message: 'Make sure the use of the microphone is necessary.', @@ -118,12 +119,12 @@ ruleTester.run('', rule, { }, { code: `navigator.permissions.query({name: "notifications"})`, - options: ['notifications'], + options: [{ permissions: ['notifications'] }], errors: 1, }, { code: `navigator.permissions.query({name: "persistent-storage"})`, - options: ['persistent-storage'], + options: [{ permissions: ['persistent-storage'] }], errors: 1, }, { @@ -149,7 +150,7 @@ ruleTester.run('', rule, { navigator.mediaDevices.getUserMedia({ audio: true, video: true }) // Sensitive for camera and microphone navigator.mediaDevices.getUserMedia({ audio: true, video: false }) // Sensitive for microphone navigator.mediaDevices.getUserMedia({ audio: false, video: { /* something */} }) // Sensitive for camera only`, - options: ['camera', 'microphone'], + options: [{ permissions: ['camera', 'microphone'] }], errors: [ { message: 'Make sure the use of the microphone and camera is necessary.', @@ -167,22 +168,22 @@ ruleTester.run('', rule, { }, { code: `Notification.requestPermission()`, - options: ['notifications'], + options: [{ permissions: ['notifications'] }], errors: 1, }, { code: `new Notification()`, - options: ['notifications'], + options: [{ permissions: ['notifications'] }], errors: 1, }, { code: `navigator.storage.persist()`, - options: ['persistent-storage'], + options: [{ permissions: ['persistent-storage'] }], errors: 1, }, { code: `navigator.storage.anyMethod()`, - options: ['persistent-storage'], + options: [{ permissions: ['persistent-storage'] }], errors: 1, }, ], diff --git a/packages/jsts/src/rules/S5693/rule.ts b/packages/jsts/src/rules/S5693/rule.ts index c88723a3a63..7567700dccc 100644 --- a/packages/jsts/src/rules/S5693/rule.ts +++ b/packages/jsts/src/rules/S5693/rule.ts @@ -29,6 +29,7 @@ import { getFullyQualifiedName, getProperty, } from '../helpers'; +import type { RuleModule } from '../../../../shared/src/types/rule'; const FORMIDABLE_MODULE = 'formidable'; const MAX_FILE_SIZE = 'maxFileSize'; @@ -44,11 +45,31 @@ const BODY_PARSER_DEFAULT_SIZE = parse('100kb'); const formidableObjects: Map = new Map(); -export const rule: Rule.RuleModule = { +export type Options = [ + { + fileUploadSizeLimit: number; + standardSizeLimit: number; + }, +]; + +export const rule: RuleModule = { meta: { messages: { safeLimit: 'Make sure the content length limit is safe here.', }, + schema: [ + { + type: 'object', + properties: { + fileUploadSizeLimit: { + type: 'integer', + }, + standardSizeLimit: { + type: 'integer', + }, + }, + }, + ], }, create(context: Rule.RuleContext) { return { @@ -217,7 +238,7 @@ function report( size?: number, useStandardSizeLimit = false, ) { - const [fileUploadSizeLimit, standardSizeLimit] = context.options; + const [{ fileUploadSizeLimit, standardSizeLimit }] = context.options as Options; const limitToCompare = useStandardSizeLimit ? standardSizeLimit : fileUploadSizeLimit; if (!size || size > limitToCompare) { context.report({ diff --git a/packages/jsts/src/rules/S5693/unit.test.ts b/packages/jsts/src/rules/S5693/unit.test.ts index 1d6aa74b7bc..df185a1f05d 100644 --- a/packages/jsts/src/rules/S5693/unit.test.ts +++ b/packages/jsts/src/rules/S5693/unit.test.ts @@ -19,9 +19,10 @@ */ import { RuleTester } from 'eslint'; import { rule } from './'; +import type { Options } from './rule'; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018, sourceType: 'module' } }); -const options = [8_000_000, 2_000_000]; +const options: Options = [{ fileUploadSizeLimit: 8_000_000, standardSizeLimit: 2_000_000 }]; ruleTester.run('Allowing requests with excessive content length is security-sensitive', rule, { valid: [ @@ -44,7 +45,7 @@ ruleTester.run('Allowing requests with excessive content length is security-sens import { formidable } from 'formidable'; const form = formidable({}); // Ok, default is used which is less than parameter `, - options: [250_000_000, 2_000_000], + options: [{ fileUploadSizeLimit: 250_000_000, standardSizeLimit: 2_000_000 }], }, ], invalid: [ @@ -230,7 +231,7 @@ ruleTester.run('Allowing requests with excessive content length is security-sens line: 5, }, ], - options: [0, 1000], + options: [{ fileUploadSizeLimit: 0, standardSizeLimit: 1000 }], }, ], }); diff --git a/packages/jsts/src/rules/S5843/rule.ts b/packages/jsts/src/rules/S5843/rule.ts index d52e6393725..bdff701a9a3 100644 --- a/packages/jsts/src/rules/S5843/rule.ts +++ b/packages/jsts/src/rules/S5843/rule.ts @@ -51,21 +51,37 @@ import { isStringRegexMethodCall, } from '../helpers/regex'; import { SONAR_RUNTIME } from '../../linter/parameters'; +import type { RuleModule } from '../../../../shared/src/types/rule'; const DEFAULT_THESHOLD = 20; -export const rule: Rule.RuleModule = { +export type Options = [ + { + threshold: number; + }, +]; + +export const rule: RuleModule = { meta: { schema: [ - { type: 'integer' }, { + type: 'object', + properties: { + threshold: { + type: 'integer', + }, + }, + }, + { + type: 'string', // internal parameter for rules having secondary locations enum: [SONAR_RUNTIME], }, ], }, create(context: Rule.RuleContext) { - const threshold = context.options.length > 0 ? context.options[0] : DEFAULT_THESHOLD; + const options = context.options as Options; + const threshold = options.length > 0 ? options[0].threshold : DEFAULT_THESHOLD; const services = context.sourceCode.parserServices; const regexNodes: estree.Node[] = []; return { diff --git a/packages/jsts/src/rules/S5843/unit.test.ts b/packages/jsts/src/rules/S5843/unit.test.ts index 26b4a6428d6..416b74bf5b0 100644 --- a/packages/jsts/src/rules/S5843/unit.test.ts +++ b/packages/jsts/src/rules/S5843/unit.test.ts @@ -20,6 +20,11 @@ import { RuleTester } from 'eslint'; import { TypeScriptRuleTester } from '../tools'; import { rule } from './'; +import type { Options } from './rule'; + +const createOptions = (threshold: number): Options => { + return [{ threshold }]; +}; const ruleTesterThreshold0 = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); ruleTesterThreshold0.run( @@ -36,67 +41,67 @@ if (isString(regex)) { }, { code: `/ /`, - options: [0], + options: createOptions(0), }, { code: `/abc/`, - options: [0], + options: createOptions(0), }, { code: `/^abc$/`, - options: [0], + options: createOptions(0), }, { code: `/(?:abc)/`, - options: [0], + options: createOptions(0), }, { code: `/(abc)/`, - options: [0], + options: createOptions(0), }, { code: `/\\w.u/`, - options: [0], + options: createOptions(0), }, { code: `RegExp('abc')`, - options: [0], + options: createOptions(0), }, { code: `new RegExp('abc')`, - options: [0], + options: createOptions(0), }, { code: 'RegExp(`abc`)', - options: [0], + options: createOptions(0), }, { code: `RegExp('[malformed')`, - options: [0], + options: createOptions(0), }, { code: `RegExp(123)`, - options: [0], + options: createOptions(0), }, { code: `RegExp(unknown)`, - options: [0], + options: createOptions(0), }, { code: `let uninitialized; RegExp(uninitialized)`, - options: [0], + options: createOptions(0), }, { code: `new Foo('abc')`, - options: [0], + options: createOptions(0), }, { code: `Foo('abc')`, - options: [0], + options: createOptions(0), }, { code: `RegExp('(a|' + 'b)')`, - options: [0], + options: createOptions(0), }, ], invalid: [ @@ -115,7 +120,7 @@ if (isString(regex)) { endColumn: 10, }, ], - options: [0], + options: createOptions(0), }, { code: `RegExp('\\r?');`, @@ -132,7 +137,7 @@ if (isString(regex)) { endColumn: 13, }, ], - options: [0], + options: createOptions(0), }, { code: `RegExp('\\\\r?');`, @@ -151,7 +156,7 @@ if (isString(regex)) { endColumn: 14, }, ], - options: [0], + options: createOptions(0), }, { code: `/(?<=abc)/`, @@ -168,7 +173,7 @@ if (isString(regex)) { endColumn: 11, }, ], - options: [0], + options: createOptions(0), }, { code: `/[a-z0-9]/`, @@ -185,7 +190,7 @@ if (isString(regex)) { endColumn: 11, }, ], - options: [0], + options: createOptions(0), }, { code: `/x*/`, @@ -202,7 +207,7 @@ if (isString(regex)) { endColumn: 5, }, ], - options: [0], + options: createOptions(0), }, { code: `/x{1,2}/`, @@ -219,22 +224,22 @@ if (isString(regex)) { endColumn: 9, }, ], - options: [0], + options: createOptions(0), }, { code: `/(?:abc)*/`, errors: 1, - options: [0], + options: createOptions(0), }, { code: `/((?:abc)*)/`, errors: 1, - options: [0], + options: createOptions(0), }, { code: `/((?:abc)*)?/`, errors: 1, - options: [0], + options: createOptions(0), }, { code: `/a|b/`, @@ -251,7 +256,7 @@ if (isString(regex)) { endColumn: 6, }, ], - options: [0], + options: createOptions(0), }, { code: `/a|b|c/`, @@ -271,12 +276,12 @@ if (isString(regex)) { endColumn: 8, }, ], - options: [0], + options: createOptions(0), }, { code: `/(?:a|b)*/`, errors: 1, - options: [0], + options: createOptions(0), }, { code: `/(?:a|b|c)*/`, @@ -303,7 +308,7 @@ if (isString(regex)) { endColumn: 13, }, ], - options: [0], + options: createOptions(0), }, { code: `/(foo)\\1/`, @@ -320,7 +325,7 @@ if (isString(regex)) { endColumn: 10, }, ], - options: [0], + options: createOptions(0), }, { code: `RegExp('x*')`, @@ -339,7 +344,7 @@ if (isString(regex)) { endColumn: 12, }, ], - options: [0], + options: createOptions(0), }, { code: `new RegExp('x*')`, @@ -358,18 +363,18 @@ if (isString(regex)) { endColumn: 16, }, ], - options: [0], + options: createOptions(0), }, { code: 'RegExp(`x*`)', errors: 1, - options: [0], + options: createOptions(0), }, { code: ` RegExp('/s*') `, - options: [0], + options: createOptions(0), errors: [ { message: JSON.stringify({ @@ -393,7 +398,7 @@ if (isString(regex)) { code: ` RegExp('|/?[a-z]') `, - options: [0], + options: createOptions(0), errors: [ { message: JSON.stringify({ @@ -431,19 +436,19 @@ ruleTesterThreshold1.run( const part2 = 'y*'; RegExp(part1 + part2); `, - options: [1], + options: createOptions(1), }, ], invalid: [ { code: `RegExp('x*' + 'y*')`, errors: 1, - options: [1], + options: createOptions(1), }, { code: `RegExp('x*' + 'y*' + 'z*')`, errors: 1, - options: [1], + options: createOptions(1), }, { code: ` @@ -481,7 +486,7 @@ ruleTesterThreshold1.run( endColumn: 27, }, ], - options: [1], + options: createOptions(1), }, ], }, @@ -495,7 +500,7 @@ typeAwareRuleTester.run( valid: [ { code: `'str'.search('abc')`, - options: [0], + options: createOptions(0), }, ], invalid: [ @@ -516,7 +521,7 @@ typeAwareRuleTester.run( endColumn: 18, }, ], - options: [0], + options: createOptions(0), }, ], }, diff --git a/packages/jsts/src/rules/S6351/rule.ts b/packages/jsts/src/rules/S6351/rule.ts index 74522e21aba..6430f6beb53 100644 --- a/packages/jsts/src/rules/S6351/rule.ts +++ b/packages/jsts/src/rules/S6351/rule.ts @@ -125,7 +125,7 @@ function extractResetRegex( node.object.type === 'Identifier' && node.property.name === 'lastIndex' ) { - const parent = getParent(context); + const parent = getParent(context, node); if (parent?.type === 'AssignmentExpression' && parent.left === node) { const variable = getVariableFromName(context, node.object.name, node); if (variable) { diff --git a/packages/jsts/src/rules/S6747/rule.ts b/packages/jsts/src/rules/S6747/rule.ts index 4586e47b5e4..0238a90a972 100644 --- a/packages/jsts/src/rules/S6747/rule.ts +++ b/packages/jsts/src/rules/S6747/rule.ts @@ -25,6 +25,7 @@ import { rules as jsxA11yRules } from 'eslint-plugin-jsx-a11y'; import { interceptReport, mergeRules } from '../helpers'; import { decorate } from './decorator'; import { TSESTree } from '@typescript-eslint/utils'; +import type { RuleModule } from '../../../../shared/src/types/rule'; const noUnkownProp = reactRules['no-unknown-property']; const decoratedNoUnkownProp = decorate(noUnkownProp); @@ -61,13 +62,32 @@ const twiceDecoratedNoUnkownProp = interceptReport(decoratedNoUnkownProp, (conte } }); -export const rule: Rule.RuleModule = { +export type Options = [ + { + ignore: Array; + }, +]; + +export const rule: RuleModule = { meta: { hasSuggestions: true, messages: { ...decoratedAriaPropsRule.meta!.messages, ...twiceDecoratedNoUnkownProp.meta!.messages, }, + schema: [ + { + type: 'object', + properties: { + ignore: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + }, + ], }, create(context: Rule.RuleContext) { diff --git a/packages/jsts/src/rules/helpers/ancestor.ts b/packages/jsts/src/rules/helpers/ancestor.ts index 7458b864d11..f766c0c783b 100644 --- a/packages/jsts/src/rules/helpers/ancestor.ts +++ b/packages/jsts/src/rules/helpers/ancestor.ts @@ -19,7 +19,7 @@ */ import { TSESTree } from '@typescript-eslint/utils'; import { Rule } from 'eslint'; -import { Node } from 'estree'; +import estree, { Node } from 'estree'; import { functionLike } from './ast'; export function findFirstMatchingLocalAncestor( @@ -54,8 +54,8 @@ export function ancestorsChain(node: TSESTree.Node, boundaryTypes: Set) return chain; } -export function getParent(context: Rule.RuleContext) { - const ancestors = context.getAncestors(); +export function getParent(context: Rule.RuleContext, node: estree.Node) { + const ancestors = context.sourceCode.getAncestors(node); return ancestors.length > 0 ? ancestors[ancestors.length - 1] : undefined; } diff --git a/packages/jsts/src/rules/helpers/decorators/interceptor.ts b/packages/jsts/src/rules/helpers/decorators/interceptor.ts index a70c10c7c55..eac5726397e 100644 --- a/packages/jsts/src/rules/helpers/decorators/interceptor.ts +++ b/packages/jsts/src/rules/helpers/decorators/interceptor.ts @@ -58,7 +58,7 @@ export function interceptReport( settings: originalContext.settings, parserPath: originalContext.parserPath, parserOptions: originalContext.parserOptions, - parserServices: originalContext.parserServices, + parserServices: originalContext.sourceCode.parserServices, sourceCode: originalContext.sourceCode, cwd: originalContext.cwd, filename: originalContext.filename, diff --git a/packages/jsts/src/rules/helpers/express.ts b/packages/jsts/src/rules/helpers/express.ts index 4d8482211bb..22944362e30 100644 --- a/packages/jsts/src/rules/helpers/express.ts +++ b/packages/jsts/src/rules/helpers/express.ts @@ -59,12 +59,13 @@ export namespace Express { export function attemptFindAppInjection( functionDef: estree.Function, context: Rule.RuleContext, + node: estree.Node, ): estree.Identifier | undefined { const app = functionDef.params.find( param => param.type === 'Identifier' && param.name === 'app', ) as estree.Identifier | undefined; if (app) { - const parent = getParent(context); + const parent = getParent(context, node); if (parent?.type === 'AssignmentExpression') { const { left } = parent; if ( @@ -192,7 +193,7 @@ export namespace Express { ':function': (node: estree.Node) => { if (!app) { const functionDef = node as estree.Function; - const injectedApp = attemptFindAppInjection(functionDef, context); + const injectedApp = attemptFindAppInjection(functionDef, context, node); if (injectedApp) { app = injectedApp; } diff --git a/packages/jsts/src/rules/helpers/module.ts b/packages/jsts/src/rules/helpers/module.ts index 58822318f24..7be0cade46b 100644 --- a/packages/jsts/src/rules/helpers/module.ts +++ b/packages/jsts/src/rules/helpers/module.ts @@ -140,7 +140,10 @@ export function getFullyQualifiedNameRaw( return null; } - const variable = getVariableFromScope(scope ?? context.getScope(), nodeToCheck.name); + const variable = getVariableFromScope( + scope ?? context.sourceCode.getScope(node), + nodeToCheck.name, + ); if (!variable || variable.defs.length > 1) { return null; diff --git a/packages/shared/src/types/rule.ts b/packages/shared/src/types/rule.ts new file mode 100644 index 00000000000..f09a33d5759 --- /dev/null +++ b/packages/shared/src/types/rule.ts @@ -0,0 +1,55 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { type Rule } from 'eslint'; + +type Schemas = Extract>; +type Schema = Schemas[number]; + +type BaseRuleModule = Omit; + +type TypedJSONSchema, string?]> = Omit< + Schema, + 'type' | 'properties' +> & + Schema['properties'] & + ( + | { + type: 'object'; + properties: { [key in keyof Options[0]]: Schema }; + } + | { + type: 'string'; + enum: Array; + } + ); + +type RuleMetaData, string?]> = Omit< + Rule.RuleMetaData, + 'schema' +> & { + schema: Array>; +}; + +export type RuleModule, string?] | null = null> = + Options extends [Record, string?] + ? Omit & { + meta: RuleMetaData; + } + : BaseRuleModule; diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ContentLengthCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ContentLengthCheck.java index 89ea0456b9e..a28b9efc16d 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ContentLengthCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ContentLengthCheck.java @@ -20,6 +20,7 @@ package org.sonar.javascript.checks; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; @@ -52,11 +53,25 @@ public class ContentLengthCheck implements EslintBasedCheck { @Override public List configurations() { - return Arrays.asList(fileUploadSizeLimit, standardSizeLimit); + return Collections.singletonList( + new Config(fileUploadSizeLimit, standardSizeLimit) + ); } @Override public String eslintKey() { return "content-length"; } + + private static class Config { + + long fileUploadSizeLimit; + + long standardSizeLimit; + + Config(long fileUploadSizeLimit, long standardSizeLimit) { + this.fileUploadSizeLimit = fileUploadSizeLimit; + this.standardSizeLimit = standardSizeLimit; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CyclomaticComplexityJavaScriptCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CyclomaticComplexityJavaScriptCheck.java index 07c15c59ecc..a6bfa27d769 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CyclomaticComplexityJavaScriptCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CyclomaticComplexityJavaScriptCheck.java @@ -46,11 +46,22 @@ public class CyclomaticComplexityJavaScriptCheck implements EslintBasedCheck { @Override public List configurations() { - return Collections.singletonList(threshold); + return Collections.singletonList( + new Config(this.threshold) + ); } @Override public String eslintKey() { return "cyclomatic-complexity"; } + + private static class Config { + + int threshold; + + Config(int threshold) { + this.threshold = threshold; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CyclomaticComplexityTypeScriptCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CyclomaticComplexityTypeScriptCheck.java index 661ef4a1582..b382c7485c8 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CyclomaticComplexityTypeScriptCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CyclomaticComplexityTypeScriptCheck.java @@ -41,11 +41,21 @@ public class CyclomaticComplexityTypeScriptCheck implements EslintBasedCheck { @Override public List configurations() { - return Collections.singletonList(threshold); + return Collections.singletonList( + new Config(this.threshold) + ); } - @Override public String eslintKey() { return "cyclomatic-complexity"; } + + private static class Config { + + int threshold; + + Config(int threshold) { + this.threshold = threshold; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ExpressionComplexityCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ExpressionComplexityCheck.java index c4729a9515d..1520cd761cb 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ExpressionComplexityCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ExpressionComplexityCheck.java @@ -42,11 +42,22 @@ public class ExpressionComplexityCheck implements EslintBasedCheck { @Override public List configurations() { - return Collections.singletonList(max); + return Collections.singletonList( + new Config(this.max) + ); } @Override public String eslintKey() { return "expression-complexity"; } + + private static class Config { + + int max; + + Config(int max) { + this.max = max; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/HardcodedCredentialsCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/HardcodedCredentialsCheck.java index 51fb620cd12..aa97d34bef4 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/HardcodedCredentialsCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/HardcodedCredentialsCheck.java @@ -20,6 +20,7 @@ package org.sonar.javascript.checks; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; @@ -43,11 +44,22 @@ public class HardcodedCredentialsCheck implements EslintBasedCheck { @Override public List configurations() { - return Arrays.asList((Object[]) credentialWords.split("\\s*,\\s*")); + return Collections.singletonList( + new Config(credentialWords.split("\\s*,\\s*")) + ); } @Override public String eslintKey() { return "no-hardcoded-credentials"; } + + private static class Config { + + String[] credentialWords; + + Config(String[] credentialWords) { + this.credentialWords = credentialWords; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ImplicitDependenciesCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ImplicitDependenciesCheck.java index 5a7e7c048f0..2662e1201ef 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ImplicitDependenciesCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ImplicitDependenciesCheck.java @@ -20,6 +20,7 @@ package org.sonar.javascript.checks; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.sonar.check.Rule; @@ -42,15 +43,26 @@ public class ImplicitDependenciesCheck implements EslintBasedCheck { @Override public List configurations() { - return Arrays - .asList(whitelist.split(",")) - .stream() - .map(String::trim) - .collect(Collectors.toList()); + return Collections.singletonList( + new Config( + Arrays.stream(whitelist.split(",")) + .map(String::trim) + .toArray(String[]::new) + ) + ); } @Override public String eslintKey() { return "no-implicit-dependencies"; } + + private static class Config { + + String[] whitelist; + + Config(String[] whitelist) { + this.whitelist = whitelist; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/IntrusivePermissionsCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/IntrusivePermissionsCheck.java index a0af5015a6b..a0867ef6d35 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/IntrusivePermissionsCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/IntrusivePermissionsCheck.java @@ -20,6 +20,7 @@ package org.sonar.javascript.checks; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; @@ -44,11 +45,24 @@ public class IntrusivePermissionsCheck implements EslintBasedCheck { @Override public List configurations() { - return Arrays.asList((Object[]) permissions.split("\\s*,\\s*")); + return Collections.singletonList( + new Config( + permissions.split("\\s*,\\s*") + ) + ); } @Override public String eslintKey() { return "no-intrusive-permissions"; } + + private static class Config { + + String[] permissions; + + Config(String[] permissions) { + this.permissions = permissions; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/MaxParameterCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/MaxParameterCheck.java index 0b4e8105fba..1b48b25e922 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/MaxParameterCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/MaxParameterCheck.java @@ -45,11 +45,22 @@ public class MaxParameterCheck implements EslintBasedCheck { @Override public List configurations() { - return Collections.singletonList(maximumFunctionParameters); + return Collections.singletonList( + new Config(maximumFunctionParameters) + ); } @Override public String eslintKey() { return "sonar-max-params"; } + + private static class Config { + + int maximumFunctionParameters; + + Config(int maximumFunctionParameters) { + this.maximumFunctionParameters = maximumFunctionParameters; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/MaxUnionSizeCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/MaxUnionSizeCheck.java index 6148f9e60aa..189d1527f42 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/MaxUnionSizeCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/MaxUnionSizeCheck.java @@ -41,11 +41,22 @@ public class MaxUnionSizeCheck implements EslintBasedCheck { @Override public List configurations() { - return Collections.singletonList(threshold); + return Collections.singletonList( + new Config(threshold) + ); } @Override public String eslintKey() { return "max-union-size"; } + + private static class Config { + + int threshold; + + Config(int threshold) { + this.threshold = threshold; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NestedControlFlowDepthCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NestedControlFlowDepthCheck.java index e27aca63894..e4f114eaad2 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NestedControlFlowDepthCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NestedControlFlowDepthCheck.java @@ -45,11 +45,20 @@ public class NestedControlFlowDepthCheck implements EslintBasedCheck { @Override public List configurations() { - return Collections.singletonList(maximumNestingLevel); + return Collections.singletonList(new Config(maximumNestingLevel)); } @Override public String eslintKey() { return "nested-control-flow"; } + + private static class Config { + + int maximumNestingLevel; + + Config(int maximumNestingLevel) { + this.maximumNestingLevel = maximumNestingLevel; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoNestedFunctionsCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoNestedFunctionsCheck.java index 82ddae3152a..0060e43134b 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoNestedFunctionsCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoNestedFunctionsCheck.java @@ -43,11 +43,22 @@ public class NoNestedFunctionsCheck implements EslintBasedCheck { @Override public List configurations() { - return Collections.singletonList(threshold); + return Collections.singletonList( + new Config(threshold) + ); } @Override public String eslintKey() { return "no-nested-functions"; } + + private static class Config { + + int threshold; + + Config(int threshold) { + this.threshold = threshold; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/RegexComplexityCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/RegexComplexityCheck.java index 64058c2aa3a..407f84fbd46 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/RegexComplexityCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/RegexComplexityCheck.java @@ -43,11 +43,22 @@ public class RegexComplexityCheck implements EslintBasedCheck { @Override public List configurations() { - return Collections.singletonList(threshold); + return Collections.singletonList( + new Config(threshold) + ); } @Override public String eslintKey() { return "regex-complexity"; } + + private static class Config { + + int threshold; + + Config(int threshold) { + this.threshold = threshold; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/TooManyLinesInFileCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/TooManyLinesInFileCheck.java index 900e51db386..f6e348d5962 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/TooManyLinesInFileCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/TooManyLinesInFileCheck.java @@ -43,11 +43,22 @@ public class TooManyLinesInFileCheck implements EslintBasedCheck { @Override public List configurations() { - return Collections.singletonList(maximum); + return Collections.singletonList( + new Config(maximum) + ); } @Override public String eslintKey() { return "sonar-max-lines"; } + + private static class Config { + + int maximum; + + Config(int maximum) { + this.maximum = maximum; + } + } } diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/TooManyLinesInFunctionCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/TooManyLinesInFunctionCheck.java index 53c36312239..1e4deed09a2 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/TooManyLinesInFunctionCheck.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/TooManyLinesInFunctionCheck.java @@ -43,11 +43,22 @@ public class TooManyLinesInFunctionCheck implements EslintBasedCheck { @Override public List configurations() { - return Collections.singletonList(max); + return Collections.singletonList( + new Config(max) + ); } @Override public String eslintKey() { return "sonar-max-lines-per-function"; } + + private static class Config { + + int maximum; + + Config(int maximum) { + this.maximum = maximum; + } + } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ContentLengthCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ContentLengthCheckTest.java index 44ae2ee4e75..35b90b09bc2 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ContentLengthCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ContentLengthCheckTest.java @@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.google.gson.Gson; import org.junit.jupiter.api.Test; class ContentLengthCheckTest { @@ -29,9 +30,11 @@ class ContentLengthCheckTest { void configurations() { ContentLengthCheck check = new ContentLengthCheck(); // default configuration - assertThat(check.configurations()).containsExactly(8_000_000L, 2_000_000L); + String defaultConfigAsString = new Gson().toJson(check.configurations()); + assertThat(defaultConfigAsString).isEqualTo("[{\"fileUploadSizeLimit\":8000000,\"standardSizeLimit\":2000000}]"); check.fileUploadSizeLimit = 42; check.standardSizeLimit = 24; - assertThat(check.configurations()).containsExactly(42L, 24L); + String customConfigAsString = new Gson().toJson(check.configurations()); + assertThat(customConfigAsString).isEqualTo("[{\"fileUploadSizeLimit\":42,\"standardSizeLimit\":24}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/CyclomaticComplexityJavaScriptCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/CyclomaticComplexityJavaScriptCheckTest.java index d3dcfe77380..0806309ac2c 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/CyclomaticComplexityJavaScriptCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/CyclomaticComplexityJavaScriptCheckTest.java @@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.google.gson.Gson; import org.junit.jupiter.api.Test; class CyclomaticComplexityJavaScriptCheckTest { @@ -28,10 +29,14 @@ class CyclomaticComplexityJavaScriptCheckTest { @Test void configurations() { CyclomaticComplexityJavaScriptCheck check = new CyclomaticComplexityJavaScriptCheck(); + // default configuration - assertThat(check.configurations()).containsExactly(10); + var defaultConfigAsString = new Gson().toJson(check.configurations()); + assertThat(defaultConfigAsString).isEqualTo("[{\"threshold\":10}]"); + // custom configuration check.threshold = 15; - assertThat(check.configurations()).containsExactly(15); + var customConfigAsString = new Gson().toJson(check.configurations()); + assertThat(customConfigAsString).isEqualTo("[{\"threshold\":15}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/CyclomaticComplexityTypeScriptCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/CyclomaticComplexityTypeScriptCheckTest.java index 6b891e0d0a1..5126e7be5e7 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/CyclomaticComplexityTypeScriptCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/CyclomaticComplexityTypeScriptCheckTest.java @@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.google.gson.Gson; import org.junit.jupiter.api.Test; class CyclomaticComplexityTypeScriptCheckTest { @@ -28,10 +29,14 @@ class CyclomaticComplexityTypeScriptCheckTest { @Test void configurations() { CyclomaticComplexityTypeScriptCheck check = new CyclomaticComplexityTypeScriptCheck(); + // default configuration - assertThat(check.configurations()).containsExactly(10); + var defaultConfigAsString = new Gson().toJson(check.configurations()); + assertThat(defaultConfigAsString).isEqualTo("[{\"threshold\":10}]"); + // custom configuration check.threshold = 15; - assertThat(check.configurations()).containsExactly(15); + var customConfigAsString = new Gson().toJson(check.configurations()); + assertThat(customConfigAsString).isEqualTo("[{\"threshold\":15}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ExpressionComplexityCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ExpressionComplexityCheckTest.java index 9fcc98ec989..21450aca2e0 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ExpressionComplexityCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ExpressionComplexityCheckTest.java @@ -31,10 +31,10 @@ void test() { ExpressionComplexityCheck check = new ExpressionComplexityCheck(); String defaultConfigAsString = new Gson().toJson(check.configurations()); - assertThat(defaultConfigAsString).isEqualTo("[3]"); + assertThat(defaultConfigAsString).isEqualTo("[{\"max\":3}]"); check.max = 10; String configAsString = new Gson().toJson(check.configurations()); - assertThat(configAsString).isEqualTo("[10]"); + assertThat(configAsString).isEqualTo("[{\"max\":10}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/HardcodedCredentialsCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/HardcodedCredentialsCheckTest.java index 740207420cf..ac1a8518200 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/HardcodedCredentialsCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/HardcodedCredentialsCheckTest.java @@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.google.gson.Gson; import org.junit.jupiter.api.Test; class HardcodedCredentialsCheckTest { @@ -29,8 +30,11 @@ class HardcodedCredentialsCheckTest { void configurations() { HardcodedCredentialsCheck check = new HardcodedCredentialsCheck(); // default configuration - assertThat(check.configurations()).containsExactly("password", "pwd", "passwd"); + String defaultConfigAsString = new Gson().toJson(check.configurations()); + assertThat(defaultConfigAsString).isEqualTo("[{\"credentialWords\":[\"password\",\"pwd\",\"passwd\"]}]"); + check.credentialWords = "foo, bar"; - assertThat(check.configurations()).containsExactly("foo", "bar"); + String customConfigAsString = new Gson().toJson(check.configurations()); + assertThat(customConfigAsString).isEqualTo("[{\"credentialWords\":[\"foo\",\"bar\"]}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ImplicitDependenciesCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ImplicitDependenciesCheckTest.java index 9f803f8777d..4e23b4d6ee4 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ImplicitDependenciesCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/ImplicitDependenciesCheckTest.java @@ -32,11 +32,11 @@ void configurations() { // default configuration String defaultConfigAsString = new Gson().toJson(check.configurations()); - assertThat(defaultConfigAsString).isEqualTo("[\"\"]"); + assertThat(defaultConfigAsString).isEqualTo("[{\"whitelist\":[\"\"]}]"); // custom configuration check.whitelist = "foo, bar"; String customConfigAsString = new Gson().toJson(check.configurations()); - assertThat(customConfigAsString).isEqualTo("[\"foo\",\"bar\"]"); + assertThat(customConfigAsString).isEqualTo("[{\"whitelist\":[\"foo\",\"bar\"]}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/IntrusivePermissionsCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/IntrusivePermissionsCheckTest.java index 9999c92c07a..69cd1773889 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/IntrusivePermissionsCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/IntrusivePermissionsCheckTest.java @@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.google.gson.Gson; import org.junit.jupiter.api.Test; class IntrusivePermissionsCheckTest { @@ -29,8 +30,10 @@ class IntrusivePermissionsCheckTest { void configurations() { IntrusivePermissionsCheck check = new IntrusivePermissionsCheck(); // default configuration - assertThat(check.configurations()).containsExactly("geolocation"); + String defaultConfigAsString = new Gson().toJson(check.configurations()); + assertThat(defaultConfigAsString).isEqualTo("[{\"permissions\":[\"geolocation\"]}]"); check.permissions = "camera, microphone"; - assertThat(check.configurations()).containsExactly("camera", "microphone"); + String customConfigAsString = new Gson().toJson(check.configurations()); + assertThat(customConfigAsString).isEqualTo("[{\"permissions\":[\"camera\",\"microphone\"]}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/MaxParameterCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/MaxParameterCheckTest.java index 913f86932b6..a284fb5d3f0 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/MaxParameterCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/MaxParameterCheckTest.java @@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.google.gson.Gson; import org.junit.jupiter.api.Test; class MaxParameterCheckTest { @@ -29,8 +30,10 @@ class MaxParameterCheckTest { void configurations() { MaxParameterCheck check = new MaxParameterCheck(); // default configuration - assertThat(check.configurations()).containsExactly(7); + String defaultConfigAsString = new Gson().toJson(check.configurations()); + assertThat(defaultConfigAsString).isEqualTo("[{\"maximumFunctionParameters\":7}]"); check.maximumFunctionParameters = 4; - assertThat(check.configurations()).containsExactly(4); + String customConfigAsString = new Gson().toJson(check.configurations()); + assertThat(customConfigAsString).isEqualTo("[{\"maximumFunctionParameters\":4}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/MaxUnionSizeCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/MaxUnionSizeCheckTest.java index 5cede35f489..2fcda6d0edb 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/MaxUnionSizeCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/MaxUnionSizeCheckTest.java @@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.google.gson.Gson; import org.junit.jupiter.api.Test; class MaxUnionSizeCheckTest { @@ -29,8 +30,9 @@ class MaxUnionSizeCheckTest { void configurations() { MaxUnionSizeCheck check = new MaxUnionSizeCheck(); // default configuration - assertThat(check.configurations()).containsExactly(3); + String defaultConfigAsString = new Gson().toJson(check.configurations()); + assertThat(defaultConfigAsString).isEqualTo("[{\"threshold\":3}]"); check.threshold = 4; - assertThat(check.configurations()).containsExactly(4); - } + String customConfigAsString = new Gson().toJson(check.configurations()); + assertThat(customConfigAsString).isEqualTo("[{\"threshold\":4}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/NestedControlFlowDepthCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/NestedControlFlowDepthCheckTest.java index 055bd6e87e3..fe415bc3fb4 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/NestedControlFlowDepthCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/NestedControlFlowDepthCheckTest.java @@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.google.gson.Gson; import org.junit.jupiter.api.Test; class NestedControlFlowDepthCheckTest { @@ -28,8 +29,11 @@ class NestedControlFlowDepthCheckTest { @Test void testConfig() { NestedControlFlowDepthCheck check = new NestedControlFlowDepthCheck(); - assertThat(check.configurations()).containsExactly(3); + var defaultConfigAsString = new Gson().toJson(check.configurations()); + assertThat(defaultConfigAsString).isEqualTo("[{\"maximumNestingLevel\":3}]"); + check.maximumNestingLevel = 42; - assertThat(check.configurations()).containsExactly(42); + var customConfigAsString = new Gson().toJson(check.configurations()); + assertThat(customConfigAsString).isEqualTo("[{\"maximumNestingLevel\":42}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/NoNestedFunctionsCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/NoNestedFunctionsCheckTest.java new file mode 100644 index 00000000000..0afaff38e3a --- /dev/null +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/NoNestedFunctionsCheckTest.java @@ -0,0 +1,39 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.javascript.checks; + +import com.google.gson.Gson; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class NoNestedFunctionsCheckTest { + + @Test + void testConfig() { + var check = new NoNestedFunctionsCheck(); + var defaultConfigAsString = new Gson().toJson(check.configurations()); + assertThat(defaultConfigAsString).isEqualTo("[{\"threshold\":4}]"); + + check.threshold = 42; + var customConfigAsString = new Gson().toJson(check.configurations()); + assertThat(customConfigAsString).isEqualTo("[{\"threshold\":42}]"); + } +} diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/RegexComplexityCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/RegexComplexityCheckTest.java index ddeaf910fb2..2025caa73bd 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/RegexComplexityCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/RegexComplexityCheckTest.java @@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.google.gson.Gson; import org.junit.jupiter.api.Test; class RegexComplexityCheckTest { @@ -29,9 +30,11 @@ class RegexComplexityCheckTest { void configurations() { RegexComplexityCheck check = new RegexComplexityCheck(); // default configuration - assertThat(check.configurations()).containsExactly(20); + var defaultConfigAsString = new Gson().toJson(check.configurations()); + assertThat(defaultConfigAsString).isEqualTo("[{\"threshold\":20}]"); // custom configuration check.threshold = 15; - assertThat(check.configurations()).containsExactly(15); + var customConfigAsString = new Gson().toJson(check.configurations()); + assertThat(customConfigAsString).isEqualTo("[{\"threshold\":15}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/TooManyLinesInFileCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/TooManyLinesInFileCheckTest.java index 37e6cfc5a54..92ce9b333b8 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/TooManyLinesInFileCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/TooManyLinesInFileCheckTest.java @@ -31,10 +31,10 @@ void test_configuration() { TooManyLinesInFileCheck check = new TooManyLinesInFileCheck(); String defaultConfigAsString = new Gson().toJson(check.configurations()); - assertThat(defaultConfigAsString).isEqualTo("[1000]"); + assertThat(defaultConfigAsString).isEqualTo("[{\"maximum\":1000}]"); check.maximum = 42; String configAsString = new Gson().toJson(check.configurations()); - assertThat(configAsString).isEqualTo("[42]"); + assertThat(configAsString).isEqualTo("[{\"maximum\":42}]"); } } diff --git a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/TooManyLinesInFunctionCheckTest.java b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/TooManyLinesInFunctionCheckTest.java index 8f98030d56a..35cdeeeb78b 100644 --- a/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/TooManyLinesInFunctionCheckTest.java +++ b/sonar-plugin/javascript-checks/src/test/java/org/sonar/javascript/checks/TooManyLinesInFunctionCheckTest.java @@ -28,13 +28,13 @@ class TooManyLinesInFunctionCheckTest { @Test void test_configuration() { - TooManyLinesInFunctionCheck check = new TooManyLinesInFunctionCheck(); + var check = new TooManyLinesInFunctionCheck(); String defaultConfigAsString = new Gson().toJson(check.configurations()); - assertThat(defaultConfigAsString).isEqualTo("[200]"); + assertThat(defaultConfigAsString).isEqualTo("[{\"maximum\":200}]"); check.max = 42; String configAsString = new Gson().toJson(check.configurations()); - assertThat(configAsString).isEqualTo("[42]"); + assertThat(configAsString).isEqualTo("[{\"maximum\":42}]"); } }