diff --git a/packages/code-analyzer-core/package.json b/packages/code-analyzer-core/package.json index 4be132e2..28d9bba5 100644 --- a/packages/code-analyzer-core/package.json +++ b/packages/code-analyzer-core/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-core", "description": "Core Package for the Salesforce Code Analyzer", - "version": "0.37.0", + "version": "0.38.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -72,4 +72,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/code-analyzer-core/src/selectors.ts b/packages/code-analyzer-core/src/selectors.ts index 252a034e..c7b6128e 100644 --- a/packages/code-analyzer-core/src/selectors.ts +++ b/packages/code-analyzer-core/src/selectors.ts @@ -24,8 +24,10 @@ export function toSelector(selectorString: string): Selector { return toComplexSelector(left, right, op); } } else { - const lastComma: number = selectorString.lastIndexOf(','); - const lastColon: number = selectorString.lastIndexOf(':'); + // If there's a close-paren in the string, only look for operators after it. + const lastCloseParen: number = Math.max(selectorString.lastIndexOf(')'), 0); + const lastComma: number = selectorString.slice(lastCloseParen).lastIndexOf(','); + const lastColon: number = selectorString.slice(lastCloseParen).lastIndexOf(':'); // BASE CASE: The selector contains no commas or colons. if (lastComma === -1 && lastColon === -1) { @@ -37,12 +39,12 @@ export function toSelector(selectorString: string): Selector { } else if (lastComma !== -1) { // Commas resolve before colons, so that "x,a:b" and "a:b,x" both resolve equivalently the combination of // "x" and "a:b". - const left: string = selectorString.slice(0, lastComma); - const right: string = selectorString.slice(lastComma + 1); + const left: string = selectorString.slice(0, lastComma + lastCloseParen); + const right: string = selectorString.slice(lastComma + lastCloseParen + 1); return toComplexSelector(left, right, ','); } else { - const left: string = selectorString.slice(0, lastColon); - const right: string = selectorString.slice(lastColon + 1); + const left: string = selectorString.slice(0, lastColon + lastCloseParen); + const right: string = selectorString.slice(lastColon + lastCloseParen + 1); return toComplexSelector(left, right, ':'); } } diff --git a/packages/code-analyzer-core/test/rule-selection.test.ts b/packages/code-analyzer-core/test/rule-selection.test.ts index a3646780..2a93240b 100644 --- a/packages/code-analyzer-core/test/rule-selection.test.ts +++ b/packages/code-analyzer-core/test/rule-selection.test.ts @@ -211,6 +211,44 @@ describe('Tests for selecting rules', () => { expect(ruleNamesFor(selection, 'stubEngine3')).toEqual(stubEngine3Rules); }); + it.each([ + { + selector: '(2,3):stubEngine1', + engines: ['stubEngine1'], + stubEngine1Rules: ['stub1RuleB', 'stub1RuleC', 'stub1RuleE'], + stubEngine2Rules: [], + stubEngine3Rules: [] + }, + { + selector: 'stubEngine1:(2,3)', + engines: ['stubEngine1'], + stubEngine1Rules: ['stub1RuleB', 'stub1RuleC', 'stub1RuleE'], + stubEngine2Rules: [], + stubEngine3Rules: [] + }, + { + selector: '(stubEngine1:2),3', + engines: ['stubEngine1', 'stubEngine2', 'stubEngine3'], + stubEngine1Rules: ['stub1RuleB', 'stub1RuleC', 'stub1RuleE'], + stubEngine2Rules: ['stub2RuleA'], + stubEngine3Rules: ['stub3RuleA'] + }, + { + selector: '3,(stubEngine1:2)', + engines: ['stubEngine1', 'stubEngine2', 'stubEngine3'], + stubEngine1Rules: ['stub1RuleB', 'stub1RuleC', 'stub1RuleE'], + stubEngine2Rules: ['stub2RuleA'], + stubEngine3Rules: ['stub3RuleA'] + } + ])('Operations within parentheses resolve before operations outside them. Case: $selector', async ({selector, engines, stubEngine1Rules, stubEngine2Rules, stubEngine3Rules}) => { + const selection: RuleSelection = await codeAnalyzer.selectRules([selector]); + + expect(selection.getEngineNames()).toEqual(engines); + expect(ruleNamesFor(selection, 'stubEngine1')).toEqual(stubEngine1Rules); + expect(ruleNamesFor(selection, 'stubEngine2')).toEqual(stubEngine2Rules); + expect(ruleNamesFor(selection, 'stubEngine3')).toEqual(stubEngine3Rules); + }) + it.each([ { case: 'colons are used and multiple selectors are provided',