Repository: rokucommunity/brighterscript
Pull request #658 (2022-08-03 18:00:54)

Fix missed const references during transpile, hover, and semantic tokens
 - add assignment expression values that are not within BinaryExpression to the `references.expressions` list.
 - For constants in assignment expressions, fixes in:
    - transpile
    - semantic tokens
    - hover
    - validation



Patch:
diff --git a/src/Scope.spec.ts b/src/Scope.spec.ts
index 0b02982c..f7274663 100644
--- a/src/Scope.spec.ts
+++ b/src/Scope.spec.ts
@@ -264,6 +264,21 @@ describe('Scope', () => {
             ]);
         });
 
+        it('detects unknown const in assignment operator', () => {
+            program.setFile('source/main.bs', `
+                sub main()
+                    value = ""
+                    value += constants.API_KEY
+                    value += API_URL
+                end sub
+            `);
+            program.validate();
+            expectDiagnostics(program, [
+                DiagnosticMessages.cannotFindName('constants'),
+                DiagnosticMessages.cannotFindName('API_URL')
+            ]);
+        });
+
         it('detects unknown local var names', () => {
             program.setFile('source/lib.bs', `
                 sub libFunc(param1)
diff --git a/src/bscPlugin/hover/HoverProcessor.spec.ts b/src/bscPlugin/hover/HoverProcessor.spec.ts
index 37bcec8b..66527da9 100644
--- a/src/bscPlugin/hover/HoverProcessor.spec.ts
+++ b/src/bscPlugin/hover/HoverProcessor.spec.ts
@@ -154,6 +154,36 @@ describe('HoverProcessor', () => {
             expect(hover?.contents).to.eql(fence('const SOME_VALUE = true'));
         });
 
+        it('finds top-level constant in assignment expression', () => {
+            program.setFile('source/main.bs', `
+                sub main()
+                    value = ""
+                    value += SOME_VALUE
+                end sub
+                const SOME_VALUE = "value"
+            `);
+            // value += SOME|_VALUE
+            let hover = program.getHover('source/main.bs', util.createPosition(3, 33))[0];
+            expect(hover?.range).to.eql(util.createRange(3, 29, 3, 39));
+            expect(hover?.contents).to.eql(fence('const SOME_VALUE = "value"'));
+        });
+
+        it('finds namespaced constant in assignment expression', () => {
+            program.setFile('source/main.bs', `
+                sub main()
+                    value = ""
+                    value += someNamespace.SOME_VALUE
+                end sub
+                namespace someNamespace
+                    const SOME_VALUE = "value"
+                end namespace
+            `);
+            // value += SOME|_VALUE
+            let hover = program.getHover('source/main.bs', util.createPosition(3, 47))[0];
+            expect(hover?.range).to.eql(util.createRange(3, 43, 3, 53));
+            expect(hover?.contents).to.eql(fence('const someNamespace.SOME_VALUE = "value"'));
+        });
+
         it('finds namespaced constant value', () => {
             program.setFile('source/main.bs', `
                 sub main()
diff --git a/src/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.ts b/src/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.ts
index dc45be94..7fcd97d0 100644
--- a/src/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.ts
+++ b/src/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.ts
@@ -320,4 +320,49 @@ describe('BrsFileSemanticTokensProcessor', () => {
             tokenModifiers: [SemanticTokenModifiers.readonly, SemanticTokenModifiers.static]
         }]);
     });
+
+    it('matches consts in assignment expressions', () => {
+        const file = program.setFile<BrsFile>('source/main.bs', `
+            sub main()
+                value = ""
+                value += constants.API_KEY
+                value += API_URL
+            end sub
+            namespace constants
+                const API_KEY = "test"
+            end namespace
+            const API_URL = "url"
+        `);
+        expectSemanticTokens(file, [
+            // value += |constants|.API_KEY
+            {
+                range: util.createRange(3, 25, 3, 34),
+                tokenType: SemanticTokenTypes.namespace
+            },
+            // value += constants.|API_KEY|
+            {
+                range: util.createRange(3, 35, 3, 42),
+                tokenType: SemanticTokenTypes.variable,
+                tokenModifiers: [SemanticTokenModifiers.readonly, SemanticTokenModifiers.static]
+            },
+            // value += |API_URL|
+            {
+                range: util.createRange(4, 25, 4, 32),
+                tokenType: SemanticTokenTypes.variable,
+                tokenModifiers: [SemanticTokenModifiers.readonly, SemanticTokenModifiers.static]
+            },
+            // const |API_KEY| = "test"
+            {
+                range: util.createRange(7, 22, 7, 29),
+                tokenType: SemanticTokenTypes.variable,
+                tokenModifiers: [SemanticTokenModifiers.readonly, SemanticTokenModifiers.static]
+            },
+            //const |API_URL| = "url"
+            {
+                range: util.createRange(9, 18, 9, 25),
+                tokenType: SemanticTokenTypes.variable,
+                tokenModifiers: [SemanticTokenModifiers.readonly, SemanticTokenModifiers.static]
+            }
+        ]);
+    });
 });
diff --git a/src/parser/Parser.spec.ts b/src/parser/Parser.spec.ts
index 7a1989c8..a215f2bd 100644
--- a/src/parser/Parser.spec.ts
+++ b/src/parser/Parser.spec.ts
@@ -57,6 +57,7 @@ describe('parser', () => {
 
         it('works for references.expressions', () => {
             const parser = Parser.parse(`
+                b += "plus-equal"
                 a += 1 + 2
                 b += getValue1() + getValue2()
                 increment++
@@ -81,6 +82,9 @@ describe('parser', () => {
                 end function
             `);
             const expected = [
+                '"plus-equal"',
+                'b',
+                'b += "plus-equal"',
                 '1',
                 '2',
                 'a',
diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts
index c53f9115..9596aa64 100644
--- a/src/parser/Parser.ts
+++ b/src/parser/Parser.ts
@@ -1053,8 +1053,10 @@ export class Parser {
                 this.currentFunctionExpression
             );
             this.addExpressionsToReferences(nameExpression);
-            //remove the right-hand-side expression from this assignment operator, and replace with the full assignment expression
-            this._references.expressions.delete(value);
+            if (isBinaryExpression(value)) {
+                //remove the right-hand-side expression from this assignment operator, and replace with the full assignment expression
+                this._references.expressions.delete(value);
+            }
             this._references.expressions.add(result);
         }
 


Test patch:
diff --git a/src/parser/tests/statement/ConstStatement.spec.ts b/src/parser/tests/statement/ConstStatement.spec.ts
index d810be16..17d73b1b 100644
--- a/src/parser/tests/statement/ConstStatement.spec.ts
+++ b/src/parser/tests/statement/ConstStatement.spec.ts
@@ -155,6 +155,26 @@ describe('ConstStatement', () => {
                 end sub
             `);
         });
+
+        it('transpiles within += operator', () => {
+            testTranspile(`
+                namespace constants
+                    const API_KEY = "test"
+                end namespace
+                const API_URL = "url"
+                sub main()
+                    value = ""
+                    value += constants.API_KEY
+                    value += API_URL
+                end sub
+            `, `
+                sub main()
+                    value = ""
+                    value += "test"
+                    value += "url"
+                end sub
+            `);
+        });
     });
 
     describe('completions', () => {