diff --git a/grammars/apex.tmLanguage b/grammars/apex.tmLanguage index a9d52cc..62891de 100644 --- a/grammars/apex.tmLanguage +++ b/grammars/apex.tmLanguage @@ -3798,7 +3798,7 @@ conditional-operator begin - (?<!\?)\?(?!\?|\.|\[) + (?<!\?)\?(?!\?|\.(?!\d)|\[) beginCaptures 0 diff --git a/grammars/apex.tmLanguage.cson b/grammars/apex.tmLanguage.cson index 4accb9c..c32ff83 100644 --- a/grammars/apex.tmLanguage.cson +++ b/grammars/apex.tmLanguage.cson @@ -2242,7 +2242,7 @@ repository: } ] 'conditional-operator': - begin: '(?conditional-operator begin - (?<!\?)\?(?!\?|\.|\[) + (?<!\?)\?(?!\?|\.(?!\d)|\[) beginCaptures 0 diff --git a/package.json b/package.json index 8535766..2920533 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "watch": "tsc -w -p .", "test:soql-tmgrammar": "vscode-tmgrammar-test -g \"./grammars/soql.tmLanguage\" \"./test/soql/*.soql\" ", "test:soql-tmgrammar-snapshots": "vscode-tmgrammar-snap -s source.soql -g \"./grammars/soql.tmLanguage\" \"./test/soql/snapshots/*.soql\" ", - "test": "npm run compile && mocha out/test/**/*.tests.js && npm run test:soql-tmgrammar && npm run test:soql-tmgrammar-snapshots", + "test": "npm run compile && mocha out/test/**/*.test.js && npm run test:soql-tmgrammar && npm run test:soql-tmgrammar-snapshots", "prepare": "npm run build", "format": "prettier --config .prettierrc.json --write './**/*.{ts,js,json,md}'" }, @@ -72,4 +72,4 @@ "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } } -} +} \ No newline at end of file diff --git a/src/apex.tmLanguage.yml b/src/apex.tmLanguage.yml index 5915856..3fe8b27 100644 --- a/src/apex.tmLanguage.yml +++ b/src/apex.tmLanguage.yml @@ -1342,7 +1342,8 @@ repository: # Only match ? if: # 1. There isn't a preceding or trailing ? (null-coalescing operator) # 2. There isn't a trailing . or [ (null-conditional operator) - begin: (? { + const input = Input.InMethod(`Decimal d = b?.34:0;`); + const tokens = await tokenize(input); + + tokens.should.deep.equal([ + Token.PrimitiveType.Decimal, + Token.Identifiers.LocalName('d'), + Token.Operators.Assignment, + Token.Variables.ReadWrite('b'), + Token.Operators.Conditional.QuestionMark, + Token.Literals.Numeric.Decimal('34'), + Token.Operators.Conditional.Colon, + Token.Literals.Numeric.Decimal('0'), + Token.Punctuation.Semicolon, + ]); + }); + + it('ternary with decimal literal with leading digits (W-8095488)', async () => { + const input = Input.InMethod(`Decimal i = b?.123:0;`); + const tokens = await tokenize(input); + + tokens.should.deep.equal([ + Token.PrimitiveType.Decimal, + Token.Identifiers.LocalName('i'), + Token.Operators.Assignment, + Token.Variables.ReadWrite('b'), + Token.Operators.Conditional.QuestionMark, + Token.Literals.Numeric.Decimal('123'), + Token.Operators.Conditional.Colon, + Token.Literals.Numeric.Decimal('0'), + Token.Punctuation.Semicolon, + ]); + }); + + it('ternary with decimal literal with spaces (W-8095488)', async () => { + const input = Input.InMethod(`Decimal d = b ? .34 : 0;`); + const tokens = await tokenize(input); + + tokens.should.deep.equal([ + Token.PrimitiveType.Decimal, + Token.Identifiers.LocalName('d'), + Token.Operators.Assignment, + Token.Variables.ReadWrite('b'), + Token.Operators.Conditional.QuestionMark, + Token.Literals.Numeric.Decimal('34'), + Token.Operators.Conditional.Colon, + Token.Literals.Numeric.Decimal('0'), + Token.Punctuation.Semicolon, + ]); + }); }); describe('Element Access', () => { diff --git a/test/field.tests.ts b/test/field.test.ts similarity index 100% rename from test/field.tests.ts rename to test/field.test.ts diff --git a/test/for-statements.tests.ts b/test/for-statements.test.ts similarity index 100% rename from test/for-statements.tests.ts rename to test/for-statements.test.ts diff --git a/test/incomplete-code.tests.ts b/test/incomplete-code.test.ts similarity index 100% rename from test/incomplete-code.tests.ts rename to test/incomplete-code.test.ts diff --git a/test/initializer-block.tests.ts b/test/initializer-block.test.ts similarity index 100% rename from test/initializer-block.tests.ts rename to test/initializer-block.test.ts diff --git a/test/interface.tests.ts b/test/interface.test.ts similarity index 100% rename from test/interface.tests.ts rename to test/interface.test.ts diff --git a/test/javadoc.tests.ts b/test/javadoc.test.ts similarity index 100% rename from test/javadoc.tests.ts rename to test/javadoc.test.ts diff --git a/test/literals.tests.ts b/test/literals.test.ts similarity index 100% rename from test/literals.tests.ts rename to test/literals.test.ts diff --git a/test/local.tests.ts b/test/local.test.ts similarity index 100% rename from test/local.tests.ts rename to test/local.test.ts diff --git a/test/method.tests.ts b/test/method.test.ts similarity index 100% rename from test/method.tests.ts rename to test/method.test.ts diff --git a/test/operator.tests.ts b/test/operator.test.ts similarity index 100% rename from test/operator.tests.ts rename to test/operator.test.ts diff --git a/test/property.tests.ts b/test/property.test.ts similarity index 100% rename from test/property.tests.ts rename to test/property.test.ts diff --git a/test/queries.tests.ts b/test/queries.test.ts similarity index 100% rename from test/queries.tests.ts rename to test/queries.test.ts diff --git a/test/repros/force-app/main/default/classes/TernaryWithDecimals_W8095488.cls b/test/repros/force-app/main/default/classes/TernaryWithDecimals_W8095488.cls new file mode 100644 index 0000000..f0d6503 --- /dev/null +++ b/test/repros/force-app/main/default/classes/TernaryWithDecimals_W8095488.cls @@ -0,0 +1,88 @@ +/** + * W-8095488: Ternary expression with decimals syntax highlighting + * + * When a ternary expression contains a decimal literal and spaces are excluded, + * the question mark may be incorrectly tokenized as a safe navigation operator + * instead of a ternary operator. + */ +public class TernaryWithDecimals_W8095488 { + + // With spaces - should work correctly + public void ternaryWithSpaces() { + Boolean b = true; + Decimal d = b ? .34 : 0; + Decimal i = b?.123 : 0; + } + + // Without spaces - potential tokenization issue + public void ternaryWithoutSpaces() { + Boolean b = true; + Decimal d = b?.34:0; + Decimal i = b?.123:0; + } + + // Mixed scenarios + public void mixedScenarios() { + Boolean b = true; + + // Space before question mark, no space after + Decimal d1 = b ?.34:0; + + // No space before, space after + Decimal d2 = b? .34:0; + + // Space after question mark, no space around colon + Decimal d3 = b? .34:0; + + // All spaces + Decimal d4 = b ? .34 : 0; + + // Integer values (for comparison) + Integer i1 = b?1:0; + Integer i2 = b ? 1 : 0; + } + + // Safe navigation operator (for comparison) + public void safeNavigationOperator() { + String s = 'test'; + Integer len = s?.length(); + + Account acc; + String name = acc?.Name; + } + + // Complex expressions + public void complexExpressions() { + Boolean b = true; + + // Ternary in switch + switch on (b) { + when true { + Decimal d = b?.34:0; + String s = 'SUCCESS'; + } + } + + // Nested ternary with decimals + Decimal nested = b ? (b?.5:.25) : .0; + + // Ternary with decimal in variable declaration + Decimal inline = b?.99:0; + } + + // Property access vs ternary + public void propertyVsTernary() { + Boolean b = true; + + // These should be ternary operators, not safe navigation + Decimal d = b?.34:0; + Decimal i = b?.123:0; + + // This is safe navigation + String type = this?.type; + } + + String type = 'test'; +} + + diff --git a/test/repros/force-app/main/default/classes/TernaryWithDecimals_W8095488.cls-meta.xml b/test/repros/force-app/main/default/classes/TernaryWithDecimals_W8095488.cls-meta.xml new file mode 100644 index 0000000..259683a --- /dev/null +++ b/test/repros/force-app/main/default/classes/TernaryWithDecimals_W8095488.cls-meta.xml @@ -0,0 +1,6 @@ + + + 58.0 + Active + + diff --git a/test/statements.tests.ts b/test/statements.test.ts similarity index 100% rename from test/statements.tests.ts rename to test/statements.test.ts diff --git a/test/switch.tests.ts b/test/switch.test.ts similarity index 100% rename from test/switch.tests.ts rename to test/switch.test.ts diff --git a/test/system.tests.ts b/test/system.test.ts similarity index 100% rename from test/system.tests.ts rename to test/system.test.ts diff --git a/test/trigger.tests.ts b/test/trigger.test.ts similarity index 100% rename from test/trigger.tests.ts rename to test/trigger.test.ts diff --git a/test/type-name.tests.ts b/test/type-name.test.ts similarity index 100% rename from test/type-name.tests.ts rename to test/type-name.test.ts diff --git a/test/xml-doc-comment.tests.ts b/test/xml-doc-comment.test.ts similarity index 100% rename from test/xml-doc-comment.tests.ts rename to test/xml-doc-comment.test.ts