diff --git a/packages/jsts/src/rules/S2598/rule.ts b/packages/jsts/src/rules/S2598/rule.ts index 8c5ea5fc80..22f60bf436 100644 --- a/packages/jsts/src/rules/S2598/rule.ts +++ b/packages/jsts/src/rules/S2598/rule.ts @@ -25,10 +25,10 @@ import * as estree from 'estree'; import { getLhsVariable, getValueOfExpression, - getObjectExpressionProperty, getVariableFromName, toEncodedMessage, getFullyQualifiedName, + getProperty, } from '../helpers'; import { SONAR_RUNTIME } from '../../linter/parameters'; @@ -115,8 +115,8 @@ function checkFormidable(context: Rule.RuleContext, callExpression: estree.CallE if (options) { report( context, - !!getObjectExpressionProperty(options, UPLOAD_DIR), - keepExtensionsValue(getObjectExpressionProperty(options, KEEP_EXTENSIONS)?.value), + !!getProperty(options, UPLOAD_DIR, context), + keepExtensionsValue(getProperty(options, KEEP_EXTENSIONS, context)?.value), callExpression, ); } @@ -136,7 +136,7 @@ function checkMulter(context: Rule.RuleContext, callExpression: estree.CallExpre return; } - const storagePropertyValue = getObjectExpressionProperty(multerOptions, STORAGE_OPTION)?.value; + const storagePropertyValue = getProperty(multerOptions, STORAGE_OPTION, context)?.value; if (storagePropertyValue) { const storageValue = getValueOfExpression(context, storagePropertyValue, 'CallExpression'); @@ -159,7 +159,7 @@ function getDiskStorageCalleeIfUnsafeStorage( const { arguments: args, callee } = storageCreation; if (args.length > 0 && isMemberWithProperty(callee, 'diskStorage')) { const storageOptions = getValueOfExpression(context, args[0], 'ObjectExpression'); - if (storageOptions && !getObjectExpressionProperty(storageOptions, DESTINATION_OPTION)) { + if (storageOptions && !getProperty(storageOptions, DESTINATION_OPTION, context)) { return callee; } } diff --git a/packages/jsts/src/rules/S2755/rule.ts b/packages/jsts/src/rules/S2755/rule.ts index 9ec43e221d..6817eecaf5 100644 --- a/packages/jsts/src/rules/S2755/rule.ts +++ b/packages/jsts/src/rules/S2755/rule.ts @@ -22,7 +22,7 @@ import { TSESTree } from '@typescript-eslint/utils'; import { Rule } from 'eslint'; import * as estree from 'estree'; -import { getObjectExpressionProperty, toEncodedMessage, getFullyQualifiedName } from '../helpers'; +import { getProperty, toEncodedMessage, getFullyQualifiedName } from '../helpers'; import { SONAR_RUNTIME } from '../../linter/parameters'; const XML_LIBRARY = 'libxmljs'; @@ -51,7 +51,7 @@ export const rule: Rule.RuleModule = { CallExpression: (node: estree.Node) => { const call = node as estree.CallExpression; if (isXmlParserCall(call)) { - const noent = getObjectExpressionProperty(call.arguments[1], 'noent'); + const noent = getProperty(call.arguments[1], 'noent', context); if (noent && isNoEntSet(noent)) { context.report({ message: toEncodedMessage('Disable access to external entities in XML parsing.', [ diff --git a/packages/jsts/src/rules/S4423/rule.lib.ts b/packages/jsts/src/rules/S4423/rule.lib.ts index 61c51ea8e4..35a54dd420 100644 --- a/packages/jsts/src/rules/S4423/rule.lib.ts +++ b/packages/jsts/src/rules/S4423/rule.lib.ts @@ -21,11 +21,7 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; -import { - getObjectExpressionProperty, - getValueOfExpression, - getFullyQualifiedName, -} from '../helpers'; +import { getProperty, getValueOfExpression, getFullyQualifiedName } from '../helpers'; const SECURE_PROTOCOL_ALLOWED_VALUES = [ 'TLSv1_2_method', @@ -51,7 +47,8 @@ export const rule: Rule.RuleModule = { objectExpression: estree.ObjectExpression | undefined, propertyName: string, ) { - const unsafeProperty = getObjectExpressionProperty(objectExpression, propertyName); + const unsafeProperty = + objectExpression && getProperty(objectExpression, propertyName, context); if (unsafeProperty) { return getValueOfExpression(context, unsafeProperty.value, 'Literal'); } @@ -89,7 +86,7 @@ export const rule: Rule.RuleModule = { }); } - const secureOptions = getObjectExpressionProperty(options, 'secureOptions'); + const secureOptions = getProperty(options, 'secureOptions', context); if (secureOptions && !isValidSecureOptions(secureOptions.value)) { context.report({ node: secureOptions, diff --git a/packages/jsts/src/rules/S4426/rule.ts b/packages/jsts/src/rules/S4426/rule.ts index cb2fee879a..66a031a3eb 100644 --- a/packages/jsts/src/rules/S4426/rule.ts +++ b/packages/jsts/src/rules/S4426/rule.ts @@ -21,7 +21,7 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; -import { isIdentifier, getValueOfExpression, getObjectExpressionProperty } from '../helpers'; +import { isIdentifier, getValueOfExpression, getProperty } from '../helpers'; const MINIMAL_MODULUS_LENGTH = 2048; const MINIMAL_DIVISOR_LENGTH = 224; @@ -72,7 +72,7 @@ export const rule: Rule.RuleModule = { } function checkRsaAndDsaOptions(algorithm: string, options: estree.Node) { - const modulusProperty = getObjectExpressionProperty(options, 'modulusLength'); + const modulusProperty = getProperty(options, 'modulusLength', context); const modulusLength = getNumericValue(modulusProperty?.value); if (modulusProperty && modulusLength && modulusLength < MINIMAL_MODULUS_LENGTH) { context.report({ @@ -84,7 +84,7 @@ export const rule: Rule.RuleModule = { }, }); } - const divisorProperty = getObjectExpressionProperty(options, 'divisorLength'); + const divisorProperty = getProperty(options, 'divisorLength', context); const divisorLength = getNumericValue(divisorProperty?.value); if (divisorProperty && divisorLength && divisorLength < MINIMAL_DIVISOR_LENGTH) { context.report({ @@ -99,7 +99,7 @@ export const rule: Rule.RuleModule = { } function checkEcCurve(options: estree.Node) { - const namedCurveProperty = getObjectExpressionProperty(options, 'namedCurve'); + const namedCurveProperty = getProperty(options, 'namedCurve', context); const namedCurve = getValueOfExpression( context, namedCurveProperty?.value, diff --git a/packages/jsts/src/rules/S4502/rule.ts b/packages/jsts/src/rules/S4502/rule.ts index a5019083a8..b981a2c0fc 100644 --- a/packages/jsts/src/rules/S4502/rule.ts +++ b/packages/jsts/src/rules/S4502/rule.ts @@ -25,11 +25,11 @@ import * as estree from 'estree'; import { isIdentifier, isLiteral, - getObjectExpressionProperty, flattenArgs, toEncodedMessage, getFullyQualifiedName, isRequireModule, + getProperty, } from '../helpers'; import { SONAR_RUNTIME } from '../../linter/parameters'; @@ -83,7 +83,7 @@ export const rule: Rule.RuleModule = { // csurf(...) if (getFullyQualifiedName(context, callee) === CSURF_MODULE) { const [args] = callExpression.arguments; - const ignoredMethods = getObjectExpressionProperty(args, 'ignoreMethods'); + const ignoredMethods = getProperty(args, 'ignoreMethods', context); if (ignoredMethods) { checkIgnoredMethods(ignoredMethods); } diff --git a/packages/jsts/src/rules/S5122/rule.ts b/packages/jsts/src/rules/S5122/rule.ts index 5a46023984..ba3891857e 100644 --- a/packages/jsts/src/rules/S5122/rule.ts +++ b/packages/jsts/src/rules/S5122/rule.ts @@ -23,9 +23,9 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; import { getUniqueWriteUsage, - getObjectExpressionProperty, toEncodedMessage, getFullyQualifiedName, + getProperty, } from '../helpers'; import { TSESTree } from '@typescript-eslint/utils'; import { SONAR_RUNTIME } from '../../linter/parameters'; @@ -63,13 +63,13 @@ export const rule: Rule.RuleModule = { return; } const [arg] = call.arguments; - let sensitiveCorsProperty = getSensitiveCorsProperty(arg); + let sensitiveCorsProperty = getSensitiveCorsProperty(arg, context); if (sensitiveCorsProperty) { report(sensitiveCorsProperty); } if (arg?.type === 'Identifier') { const usage = getUniqueWriteUsage(context, arg.name); - sensitiveCorsProperty = getSensitiveCorsProperty(usage); + sensitiveCorsProperty = getSensitiveCorsProperty(usage, context); if (sensitiveCorsProperty) { report(sensitiveCorsProperty, arg); } @@ -82,7 +82,7 @@ export const rule: Rule.RuleModule = { }, ObjectExpression(node: estree.Node) { - const objProperty = getObjectExpressionProperty(node, CORS_HEADER); + const objProperty = getProperty(node, CORS_HEADER, context); if (objProperty && isAnyDomain(objProperty.value)) { report(objProperty); } @@ -103,8 +103,9 @@ function isAnyDomain(node: estree.Node) { function getSensitiveCorsProperty( node: estree.Node | undefined | null, + context: Rule.RuleContext, ): estree.Property | undefined { - const originProperty = getObjectExpressionProperty(node, 'origin'); + const originProperty = getProperty(node, 'origin', context); if (originProperty && isAnyDomain(originProperty.value)) { return originProperty; } diff --git a/packages/jsts/src/rules/S5332/rule.lib.ts b/packages/jsts/src/rules/S5332/rule.lib.ts index c6113374d7..98e95dba6d 100644 --- a/packages/jsts/src/rules/S5332/rule.lib.ts +++ b/packages/jsts/src/rules/S5332/rule.lib.ts @@ -22,12 +22,7 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; import { URL } from 'url'; -import { - getValueOfExpression, - getObjectExpressionProperty, - getParent, - getFullyQualifiedName, -} from '../helpers'; +import { getValueOfExpression, getParent, getFullyQualifiedName, getProperty } from '../helpers'; import { normalizeFQN } from '../helpers/aws/cdk'; const INSECURE_PROTOCOLS = ['http://', 'ftp://', 'telnet://']; @@ -71,22 +66,22 @@ export const rule: Rule.RuleModule = { const firstArgValue = getValueOfExpression(context, firstArg, 'ObjectExpression'); - const ses = getObjectExpressionProperty(firstArgValue, 'SES'); + const ses = getProperty(firstArgValue, 'SES', context); if (ses && usesSesCommunication(ses)) { return; } - const secure = getObjectExpressionProperty(firstArgValue, 'secure'); + const secure = getProperty(firstArgValue, 'secure', context); if (secure && (secure.value.type !== 'Literal' || secure.value.raw !== 'false')) { return; } - const requireTls = getObjectExpressionProperty(firstArgValue, 'requireTLS'); + const requireTls = getProperty(firstArgValue, 'requireTLS', context); if (requireTls && (requireTls.value.type !== 'Literal' || requireTls.value.raw !== 'false')) { return; } - const port = getObjectExpressionProperty(firstArgValue, 'port'); + const port = getProperty(firstArgValue, 'port', context); if (port && (port.value.type !== 'Literal' || port.value.raw === '465')) { return; } @@ -102,14 +97,14 @@ export const rule: Rule.RuleModule = { const ses = getValueOfExpression( context, - getObjectExpressionProperty(configuration, 'ses')?.value, + getProperty(configuration, 'ses', context)?.value, 'NewExpression', ); if (!ses || normalizeFQN(getFullyQualifiedName(context, ses)) !== '@aws_sdk.client_ses.SES') { return false; } - const aws = getObjectExpressionProperty(configuration, 'aws'); + const aws = getProperty(configuration, 'aws', context); if ( !aws || normalizeFQN(getFullyQualifiedName(context, aws.value)) !== '@aws_sdk.client_ses' @@ -137,7 +132,7 @@ export const rule: Rule.RuleModule = { return; } const firstArgValue = getValueOfExpression(context, firstArg, 'ObjectExpression'); - const secure = getObjectExpressionProperty(firstArgValue, 'secure'); + const secure = getProperty(firstArgValue, 'secure', context); if (secure && secure.value.type === 'Literal' && secure.value.raw === 'false') { context.report({ node: callExpression.callee, diff --git a/packages/jsts/src/rules/S5527/rule.ts b/packages/jsts/src/rules/S5527/rule.ts index a11b992d32..7b1bbeb54b 100644 --- a/packages/jsts/src/rules/S5527/rule.ts +++ b/packages/jsts/src/rules/S5527/rule.ts @@ -24,9 +24,9 @@ import * as estree from 'estree'; import { getValueOfExpression, getPropertyWithValue, - getObjectExpressionProperty, toEncodedMessage, getFullyQualifiedName, + getProperty, } from '../helpers'; import { SONAR_RUNTIME } from '../../linter/parameters'; import { childrenOf } from '../../linter'; @@ -73,9 +73,10 @@ export const rule: Rule.RuleModule = { secondaryMessages.push(SECONDARY_MESSAGE); shouldReport = true; } - const checkServerIdentityProperty = getObjectExpressionProperty( + const checkServerIdentityProperty = getProperty( argumentValue, 'checkServerIdentity', + context, ); if ( checkServerIdentityProperty && diff --git a/packages/jsts/src/rules/S5659/rule.ts b/packages/jsts/src/rules/S5659/rule.ts index 69941b9475..2325c83dbe 100644 --- a/packages/jsts/src/rules/S5659/rule.ts +++ b/packages/jsts/src/rules/S5659/rule.ts @@ -24,10 +24,10 @@ import * as estree from 'estree'; import { getPropertyWithValue, getValueOfExpression, - getObjectExpressionProperty, toEncodedMessage, isNullLiteral, getFullyQualifiedName, + getProperty, } from '../helpers'; import { SONAR_RUNTIME } from '../../linter/parameters'; @@ -76,7 +76,7 @@ export const rule: Rule.RuleModule = { thirdArgumentValue: estree.ObjectExpression, secondaryLocations: estree.Node[], ) { - const algorithmsProperty = getObjectExpressionProperty(thirdArgumentValue, 'algorithms'); + const algorithmsProperty = getProperty(thirdArgumentValue, 'algorithms', context); if (!algorithmsProperty) { if (isNullLiteral(publicKey)) { raiseIssueOn(callExpression.callee, VERIFY_MESSAGE, secondaryLocations); diff --git a/packages/jsts/src/rules/S5691/rule.ts b/packages/jsts/src/rules/S5691/rule.ts index 67454cfad8..5046cdf319 100644 --- a/packages/jsts/src/rules/S5691/rule.ts +++ b/packages/jsts/src/rules/S5691/rule.ts @@ -21,11 +21,7 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; -import { - getUniqueWriteUsage, - getObjectExpressionProperty, - getFullyQualifiedName, -} from '../helpers'; +import { getUniqueWriteUsage, getFullyQualifiedName, getProperty } from '../helpers'; const SERVE_STATIC = 'serve-static'; @@ -46,7 +42,7 @@ export const rule: Rule.RuleModule = { options = getUniqueWriteUsage(context, options.name); } - const dotfilesProperty = getObjectExpressionProperty(options, 'dotfiles'); + const dotfilesProperty = getProperty(options, 'dotfiles', context); if ( dotfilesProperty?.value.type === 'Literal' && dotfilesProperty.value.value === 'allow' diff --git a/packages/jsts/src/rules/S5693/rule.ts b/packages/jsts/src/rules/S5693/rule.ts index c22e8c27ff..f4827c9ebd 100644 --- a/packages/jsts/src/rules/S5693/rule.ts +++ b/packages/jsts/src/rules/S5693/rule.ts @@ -26,8 +26,8 @@ import { parse } from 'bytes'; import { getLhsVariable, getValueOfExpression, - getObjectExpressionProperty, getFullyQualifiedName, + getProperty, } from '../helpers'; const FORMIDABLE_MODULE = 'formidable'; @@ -116,7 +116,7 @@ function checkFormidable(context: Rule.RuleContext, callExpression: estree.CallE const options = getValueOfExpression(context, callExpression.arguments[0], 'ObjectExpression'); if (options) { - const property = getObjectExpressionProperty(options, MAX_FILE_SIZE); + const property = getProperty(options, MAX_FILE_SIZE, context); checkSize(context, callExpression, property, FORMIDABLE_DEFAULT_SIZE); } } @@ -136,9 +136,9 @@ function checkMulter(context: Rule.RuleContext, callExpression: estree.CallExpre return; } - const limitsPropertyValue = getObjectExpressionProperty(multerOptions, LIMITS_OPTION)?.value; + const limitsPropertyValue = getProperty(multerOptions, LIMITS_OPTION, context)?.value; if (limitsPropertyValue && limitsPropertyValue.type === 'ObjectExpression') { - const fileSizeProperty = getObjectExpressionProperty(limitsPropertyValue, FILE_SIZE_OPTION); + const fileSizeProperty = getProperty(limitsPropertyValue, FILE_SIZE_OPTION, context); checkSize(context, callExpression, fileSizeProperty); } @@ -158,14 +158,14 @@ function checkBodyParser(context: Rule.RuleContext, callExpression: estree.CallE return; } - const limitsProperty = getObjectExpressionProperty(options, LIMITS_OPTION); + const limitsProperty = getProperty(options, LIMITS_OPTION, context); checkSize(context, callExpression, limitsProperty, BODY_PARSER_DEFAULT_SIZE, true); } function checkSize( context: Rule.RuleContext, callExpr: estree.CallExpression, - property?: estree.Property, + property?: estree.Property | null, defaultLimit?: number, useStandardSizeLimit = false, ) { diff --git a/packages/jsts/src/rules/S5730/rule.ts b/packages/jsts/src/rules/S5730/rule.ts index 21a0d6359b..cc215efbd6 100644 --- a/packages/jsts/src/rules/S5730/rule.ts +++ b/packages/jsts/src/rules/S5730/rule.ts @@ -21,7 +21,7 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; -import { Express, getObjectExpressionProperty, getFullyQualifiedName } from '../helpers'; +import { Express, getFullyQualifiedName, getProperty } from '../helpers'; const HELMET = 'helmet'; const HELMET_CSP = 'helmet-csp'; @@ -43,10 +43,10 @@ function findDirectivesWithMissingMixedContentPropertyFromHelmet( const { arguments: args } = node; if (args.length === 1) { const [options] = args; - const maybeDirectives = getObjectExpressionProperty(options, DIRECTIVES); + const maybeDirectives = getProperty(options, DIRECTIVES, context); if ( maybeDirectives && - isMissingMixedContentProperty(maybeDirectives) && + isMissingMixedContentProperty(maybeDirectives, context) && isValidHelmetModuleCall(context, node) ) { sensitive = maybeDirectives; @@ -60,9 +60,12 @@ function isValidHelmetModuleCall(context: Rule.RuleContext, callExpr: estree.Cal return fqn === `${HELMET}.${CONTENT_SECURITY_POLICY}` || fqn === HELMET_CSP; } -function isMissingMixedContentProperty(directives: estree.Property): boolean { +function isMissingMixedContentProperty( + directives: estree.Property, + context: Rule.RuleContext, +): boolean { return !( - Boolean(getObjectExpressionProperty(directives.value, BLOCK_ALL_MIXED_CONTENT_CAMEL)) || - Boolean(getObjectExpressionProperty(directives.value, BLOCK_ALL_MIXED_CONTENT_HYPHEN)) + Boolean(getProperty(directives.value, BLOCK_ALL_MIXED_CONTENT_CAMEL, context)) || + Boolean(getProperty(directives.value, BLOCK_ALL_MIXED_CONTENT_HYPHEN, context)) ); } diff --git a/packages/jsts/src/rules/S5732/rule.ts b/packages/jsts/src/rules/S5732/rule.ts index e3ee3c500f..b84b92e33d 100644 --- a/packages/jsts/src/rules/S5732/rule.ts +++ b/packages/jsts/src/rules/S5732/rule.ts @@ -21,7 +21,7 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; -import { Express, getFullyQualifiedName, getObjectExpressionProperty } from '../helpers'; +import { Express, getFullyQualifiedName, getProperty } from '../helpers'; const HELMET = 'helmet'; const HELMET_CSP = 'helmet-csp'; @@ -43,9 +43,9 @@ function findDirectivesWithSensitiveFrameAncestorsPropertyFromHelmet( const { arguments: args } = node; if (isValidHelmetModuleCall(context, node) && args.length === 1) { const [options] = args; - const maybeDirectives = getObjectExpressionProperty(options, DIRECTIVES); + const maybeDirectives = getProperty(options, DIRECTIVES, context); if (maybeDirectives) { - const maybeFrameAncestors = getFrameAncestorsProperty(maybeDirectives); + const maybeFrameAncestors = getFrameAncestorsProperty(maybeDirectives, context); if (!maybeFrameAncestors) { return [maybeDirectives]; } @@ -75,10 +75,13 @@ function isSetNoneFrameAncestorsProperty(frameAncestors: estree.Property): boole ); } -function getFrameAncestorsProperty(directives: estree.Property): estree.Property | undefined { +function getFrameAncestorsProperty( + directives: estree.Property, + context: Rule.RuleContext, +): estree.Property | undefined { const propertyKeys = [FRAME_ANCESTORS_CAMEL, FRAME_ANCESTORS_HYPHEN]; for (const propertyKey of propertyKeys) { - const maybeProperty = getObjectExpressionProperty(directives.value, propertyKey); + const maybeProperty = getProperty(directives.value, propertyKey, context); if (maybeProperty) { return maybeProperty; } diff --git a/packages/jsts/src/rules/S5736/rule.ts b/packages/jsts/src/rules/S5736/rule.ts index 09f65da585..98be11b83e 100644 --- a/packages/jsts/src/rules/S5736/rule.ts +++ b/packages/jsts/src/rules/S5736/rule.ts @@ -21,12 +21,7 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; -import { - Express, - getPropertyWithValue, - getObjectExpressionProperty, - getFullyQualifiedName, -} from '../helpers'; +import { Express, getPropertyWithValue, getFullyQualifiedName, getProperty } from '../helpers'; const HELMET = 'helmet'; const POLICY = 'policy'; @@ -53,7 +48,7 @@ function findNoReferrerPolicyPropertyFromHelmet( if (fqn === HELMET && options.type === 'ObjectExpression') { sensitive = getPropertyWithValue(context, options, REFERRER_POLICY, false); } else if (fqn === `${HELMET}.${REFERRER_POLICY}`) { - const maybePolicy = getObjectExpressionProperty(options, POLICY); + const maybePolicy = getProperty(options, POLICY, context); if (maybePolicy && !isSafePolicy(maybePolicy)) { sensitive = maybePolicy; } diff --git a/packages/jsts/src/rules/S5739/rule.ts b/packages/jsts/src/rules/S5739/rule.ts index 34a11a1375..bb3dded574 100644 --- a/packages/jsts/src/rules/S5739/rule.ts +++ b/packages/jsts/src/rules/S5739/rule.ts @@ -24,9 +24,9 @@ import * as estree from 'estree'; import { Express, getPropertyWithValue, - getObjectExpressionProperty, getValueOfExpression, getFullyQualifiedName, + getProperty, } from '../helpers'; const HSTS = 'hsts'; @@ -76,7 +76,7 @@ function findSensitiveMaxAge( options: estree.ObjectExpression, ): estree.Property | undefined { if (isHstsMiddlewareNode(context, middleware)) { - const maybeMaxAgeProperty = getObjectExpressionProperty(options, MAX_AGE); + const maybeMaxAgeProperty = getProperty(options, MAX_AGE, context); if (maybeMaxAgeProperty) { const maybeMaxAgeValue = getValueOfExpression(context, maybeMaxAgeProperty.value, 'Literal'); if ( diff --git a/packages/jsts/src/rules/S5757/rule.ts b/packages/jsts/src/rules/S5757/rule.ts index 98fe8fdb29..483f7e605b 100644 --- a/packages/jsts/src/rules/S5757/rule.ts +++ b/packages/jsts/src/rules/S5757/rule.ts @@ -24,9 +24,9 @@ import * as estree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; import { getValueOfExpression, - getObjectExpressionProperty, toEncodedMessage, getFullyQualifiedName, + getProperty, } from '../helpers'; import { SONAR_RUNTIME } from '../../linter/parameters'; @@ -61,7 +61,7 @@ export const rule: Rule.RuleModule = { // Argument exists but its value is unknown return; } - const secrets = getObjectExpressionProperty(firstArgument, 'secrets'); + const secrets = getProperty(firstArgument, 'secrets', context); if ( secrets && secrets.value.type === 'ArrayExpression' && diff --git a/packages/jsts/src/rules/S5759/rule.ts b/packages/jsts/src/rules/S5759/rule.ts index 70d723619a..620b9d2f3b 100644 --- a/packages/jsts/src/rules/S5759/rule.ts +++ b/packages/jsts/src/rules/S5759/rule.ts @@ -22,10 +22,10 @@ import { Rule } from 'eslint'; import * as estree from 'estree'; import { - getObjectExpressionProperty, getValueOfExpression, toEncodedMessage, getFullyQualifiedName, + getProperty, } from '../helpers'; import { SONAR_RUNTIME } from '../../linter/parameters'; @@ -44,7 +44,7 @@ export const rule: Rule.RuleModule = { const call = node as estree.CallExpression; const { callee, arguments: args } = call; if (isSensitiveFQN(context, call) && args.length > 0) { - const xfwdProp = getObjectExpressionProperty(args[0], 'xfwd'); + const xfwdProp = getProperty(args[0], 'xfwd', context); if (!xfwdProp) { return; } diff --git a/packages/jsts/src/rules/helpers/ast.ts b/packages/jsts/src/rules/helpers/ast.ts index e469784f2b..80f5bd52f7 100644 --- a/packages/jsts/src/rules/helpers/ast.ts +++ b/packages/jsts/src/rules/helpers/ast.ts @@ -20,7 +20,7 @@ import { TSESTree } from '@typescript-eslint/utils'; import { Rule, Scope } from 'eslint'; import * as estree from 'estree'; -import { flatMap, getFullyQualifiedName, toEncodedMessage } from '.'; +import { findFirstMatchingAncestor, flatMap, getFullyQualifiedName, toEncodedMessage } from '.'; export type Node = estree.Node | TSESTree.Node; @@ -412,29 +412,13 @@ function resolveIdentifiersAcc( } } -export function getObjectExpressionProperty( - node: estree.Node | undefined | null, - propertyKey: string, -): estree.Property | undefined { - if (node?.type === 'ObjectExpression') { - const properties = node.properties.filter( - p => - p.type === 'Property' && - (isIdentifier(p.key, propertyKey) || (isLiteral(p.key) && p.key.value === propertyKey)), - ) as estree.Property[]; - // if property is duplicated, we return the last defined - return properties[properties.length - 1]; - } - return undefined; -} - export function getPropertyWithValue( context: Rule.RuleContext, objectExpression: estree.ObjectExpression, propertyName: string, propertyValue: estree.Literal['value'], ): estree.Property | undefined { - const maybeProperty = getObjectExpressionProperty(objectExpression, propertyName); + const maybeProperty = getProperty(objectExpression, propertyName, context); if (maybeProperty) { const maybePropertyValue = getValueOfExpression(context, maybeProperty.value, 'Literal'); if (maybePropertyValue?.value === propertyValue) { @@ -444,11 +428,35 @@ export function getPropertyWithValue( return undefined; } +function getPropertyFromSpreadElement( + spreadElement: estree.SpreadElement, + key: string, + ctx: Rule.RuleContext, +): estree.Property | null | undefined { + const props = getValueOfExpression(ctx, spreadElement.argument, 'ObjectExpression'); + const recursiveDefinition = findFirstMatchingAncestor( + spreadElement.argument as TSESTree.Node, + node => node === props, + ); + if (recursiveDefinition || props === undefined) { + return undefined; + } + return getProperty(props, key, ctx); +} + +/** + * Retrieves the property with the specified key from the given node. + * @returns The property if found, or null if not found, or undefined if property not found and one of the properties + * is an unresolved SpreadElement. + */ export function getProperty( - expr: estree.ObjectExpression, + expr: estree.Node | undefined | null, key: string, ctx: Rule.RuleContext, ): estree.Property | null | undefined { + if (expr?.type !== 'ObjectExpression') { + return null; + } let unresolvedSpreadElement = false; for (let i = expr.properties.length - 1; i >= 0; --i) { const property = expr.properties[i]; @@ -456,14 +464,11 @@ export function getProperty( return property; } if (property.type === 'SpreadElement') { - const props = getValueOfExpression(ctx, property.argument, 'ObjectExpression'); - if (props !== undefined) { - const prop = getProperty(props, key, ctx); - if (prop !== null) { - return prop; - } - } else { + const prop = getPropertyFromSpreadElement(property, key, ctx); + if (prop === undefined) { unresolvedSpreadElement = true; + } else if (prop !== null) { + return prop; } } } diff --git a/packages/jsts/src/rules/helpers/cookie-flag-check.ts b/packages/jsts/src/rules/helpers/cookie-flag-check.ts index 92a9ad1801..99504914e1 100644 --- a/packages/jsts/src/rules/helpers/cookie-flag-check.ts +++ b/packages/jsts/src/rules/helpers/cookie-flag-check.ts @@ -23,9 +23,9 @@ import * as estree from 'estree'; import { isIdentifier, getValueOfExpression, - getObjectExpressionProperty, toEncodedMessage, getFullyQualifiedName, + getProperty, } from '.'; export class CookieFlagCheck { @@ -109,7 +109,7 @@ export class CookieFlagCheck { if (!objectExpression) { return; } - const cookieProperty = getObjectExpressionProperty(objectExpression, 'cookie'); + const cookieProperty = getProperty(objectExpression, 'cookie', this.context); if (!cookieProperty) { return; } @@ -136,7 +136,7 @@ export class CookieFlagCheck { objectExpression: estree.ObjectExpression, callExpression: estree.CallExpression, ) { - const flagProperty = getObjectExpressionProperty(cookiePropertyValue, this.flag); + const flagProperty = getProperty(cookiePropertyValue, this.flag, this.context); if (flagProperty) { const flagPropertyValue = getValueOfExpression(this.context, flagProperty.value, 'Literal'); if (flagPropertyValue?.value === false) { diff --git a/packages/jsts/tests/rules/helpers/ast.test.ts b/packages/jsts/tests/rules/helpers/ast.test.ts new file mode 100644 index 0000000000..51805e704e --- /dev/null +++ b/packages/jsts/tests/rules/helpers/ast.test.ts @@ -0,0 +1,78 @@ +/* + * 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 path from 'path'; + +import { Linter, Rule } from 'eslint'; +import { getProperty } from '../../../src/rules/helpers/ast'; + +import { parseJavaScriptSourceFile } from '../../tools'; + +describe('getProperty', () => { + it.each([ + [ + 'should read property of simple object', + 'normalObject.js', + 'foo', + property => expect(property.value.type).toEqual('Literal'), + ], + [ + 'should return null if key not found in simple object', + 'normalObject.js', + 'baz', + property => expect(property).toBeNull(), + ], + [ + 'should read property of object with a recursive spread operator', + 'objectWithSpread.js', + 'bar', + property => expect(property.value.type).toEqual('Literal'), + ], + [ + 'should read undefined of object with a recursive spread operator if key not found', + 'objectWithSpread.js', + 'baz', + property => expect(property).toBeUndefined(), + ], + ])('it %s', async (_: string, fixtureFile: string, key: string, verifier: (property) => void) => { + const baseDir = path.join(__dirname, 'fixtures'); + + const linter = new Linter(); + linter.defineRule('custom-rule-file', { + create(context: Rule.RuleContext) { + return { + 'ExpressionStatement ObjectExpression': node => { + const property = getProperty(node, key, context); + verifier(property); + }, + }; + }, + } as Rule.RuleModule); + + const filePath = path.join(baseDir, fixtureFile); + const sourceCode = await parseJavaScriptSourceFile(filePath); + + linter.verify( + sourceCode, + { rules: { 'custom-rule-file': 'error' } }, + { filename: filePath, allowInlineConfig: false }, + ); + }); +}); diff --git a/packages/jsts/tests/rules/helpers/express.test.ts b/packages/jsts/tests/rules/helpers/express.test.ts index ca0742b6ae..c0a44f8ac8 100644 --- a/packages/jsts/tests/rules/helpers/express.test.ts +++ b/packages/jsts/tests/rules/helpers/express.test.ts @@ -19,19 +19,19 @@ */ import { Rule, RuleTester } from 'eslint'; import * as estree from 'estree'; -import { Express, getObjectExpressionProperty } from '../../../src/rules/helpers'; +import { Express, getProperty } from '../../../src/rules/helpers'; const rule = Express.SensitiveMiddlewarePropertyRule( - (_context: Rule.RuleContext, node: estree.CallExpression): estree.Property[] => { + (context: Rule.RuleContext, node: estree.CallExpression): estree.Property[] => { const sensitives: estree.Property[] = []; const { callee, arguments: args } = node; if (callee.type === 'Identifier' && callee.name === 'middleware' && args.length === 1) { const [options] = args; - const maybeFoo = getObjectExpressionProperty(options, 'foo'); + const maybeFoo = getProperty(options, 'foo', context); if (maybeFoo) { sensitives.push(maybeFoo); } - const maybeBar = getObjectExpressionProperty(options, 'bar'); + const maybeBar = getProperty(options, 'bar', context); if (maybeBar) { sensitives.push(maybeBar); } diff --git a/packages/jsts/tests/rules/helpers/fixtures/normalObject.js b/packages/jsts/tests/rules/helpers/fixtures/normalObject.js new file mode 100644 index 0000000000..5575dd48af --- /dev/null +++ b/packages/jsts/tests/rules/helpers/fixtures/normalObject.js @@ -0,0 +1,7 @@ +const items = [{ foo: true, bar: false }]; +items.forEach(item => { + item = { + foo: false, + bar: true, + }; +}); diff --git a/packages/jsts/tests/rules/helpers/fixtures/objectWithSpread.js b/packages/jsts/tests/rules/helpers/fixtures/objectWithSpread.js new file mode 100644 index 0000000000..45c30631c6 --- /dev/null +++ b/packages/jsts/tests/rules/helpers/fixtures/objectWithSpread.js @@ -0,0 +1,7 @@ +const items = [{ foo: true, bar: false }]; +items.forEach(item => { + item = { + ...item, + bar: true, + }; +}); diff --git a/packages/jsts/tests/tools/sonar-runtime/rule/no-missing-sonar-runtime.ts b/packages/jsts/tests/tools/sonar-runtime/rule/no-missing-sonar-runtime.ts index 9d056f6e1f..1a44ac7941 100644 --- a/packages/jsts/tests/tools/sonar-runtime/rule/no-missing-sonar-runtime.ts +++ b/packages/jsts/tests/tools/sonar-runtime/rule/no-missing-sonar-runtime.ts @@ -22,7 +22,7 @@ import { Rule } from 'eslint'; import { getFullyQualifiedName, getImportDeclarations, - getObjectExpressionProperty, + getProperty, getUniqueWriteUsage, isIdentifier, } from '../../../../src/rules/helpers'; @@ -60,17 +60,17 @@ export const rule: Rule.RuleModule = { if (isSecondaryLocationEnabled) { return; } - const maybeMeta = getObjectExpressionProperty(node, 'meta'); + const maybeMeta = getProperty(node, 'meta', context); if (!maybeMeta) { return; } - const maybeSchema = getObjectExpressionProperty(maybeMeta.value, 'schema'); + const maybeSchema = getProperty(maybeMeta.value, 'schema', context); if (maybeSchema?.value.type !== 'ArrayExpression') { return; } const schema = maybeSchema.value; for (const element of schema.elements) { - const maybeEnum = getObjectExpressionProperty(element, 'enum'); + const maybeEnum = getProperty(element, 'enum', context); if (maybeEnum) { isSecondaryLocationEnabled = maybeEnum.value.type === 'ArrayExpression' &&