Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion src/bscPlugin/transpile/BrsFilePreTranspileProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,64 @@

}

/**
* Recursively resolve a const value until we get to the final resolved expression
*/
private resolveConstValue(value: Expression, scope: Scope | undefined, containingNamespace: string | undefined, visited = new Set<string>()): Expression {
// If it's already a literal, return it as-is
if (isLiteralExpression(value)) {
return value;
}

Check failure on line 219 in src/bscPlugin/transpile/BrsFilePreTranspileProcessor.ts

View workflow job for this annotation

GitHub Actions / copilot

Expected longform property syntax

// If it's a variable expression, try to resolve it as a const
if (isVariableExpression(value)) {
const entityName = value.name.text.toLowerCase();

// Prevent infinite recursion by tracking visited constants
if (visited.has(entityName)) {
return value; // Return the original value to avoid infinite loop
}

Check failure on line 228 in src/bscPlugin/transpile/BrsFilePreTranspileProcessor.ts

View workflow job for this annotation

GitHub Actions / copilot

Expected longform property syntax
visited.add(entityName);

const constStatement = scope?.getConstFileLink(entityName, containingNamespace)?.item;
if (constStatement) {
// Recursively resolve the const value
return this.resolveConstValue(constStatement.value, scope, containingNamespace, visited);
}
}

// If it's a dotted get expression (e.g., namespace.const), try to resolve it
if (isDottedGetExpression(value)) {
const parts = util.splitExpression(value);
const processedNames: string[] = [];

for (let part of parts) {
if (isVariableExpression(part) || isDottedGetExpression(part)) {
processedNames.push(part?.name?.text?.toLowerCase());
} else {
return value; // Can't resolve further
}
}

const entityName = processedNames.join('.');

// Prevent infinite recursion
if (visited.has(entityName)) {
return value;
}

Check failure on line 256 in src/bscPlugin/transpile/BrsFilePreTranspileProcessor.ts

View workflow job for this annotation

GitHub Actions / copilot

Expected longform property syntax
visited.add(entityName);

const constStatement = scope?.getConstFileLink(entityName, containingNamespace)?.item;
if (constStatement) {
// Recursively resolve the const value
return this.resolveConstValue(constStatement.value, scope, containingNamespace, visited);
}
}

Check failure on line 264 in src/bscPlugin/transpile/BrsFilePreTranspileProcessor.ts

View workflow job for this annotation

GitHub Actions / copilot

Expected longform property syntax

// Return the value as-is if we can't resolve it further
return value;
}

private processExpression(ternaryExpression: Expression, scope: Scope | undefined) {
let containingNamespace = this.event.file.getNamespaceStatementForPosition(ternaryExpression.range.start)?.getName(ParseMode.BrighterScript);

Expand All @@ -223,13 +281,14 @@
} else {
return;
}

Check failure on line 284 in src/bscPlugin/transpile/BrsFilePreTranspileProcessor.ts

View workflow job for this annotation

GitHub Actions / copilot

Expected longform property syntax
let value: Expression;

//did we find a const? transpile the value
let constStatement = scope?.getConstFileLink(entityName, containingNamespace)?.item;
if (constStatement) {
value = constStatement.value;
// Recursively resolve the const value to its final form
value = this.resolveConstValue(constStatement.value, scope, containingNamespace);
} else {
//did we find an enum member? transpile that
let enumInfo = this.getEnumInfo(entityName, containingNamespace, scope);
Expand Down
103 changes: 103 additions & 0 deletions src/parser/tests/statement/ConstStatement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,109 @@ describe('ConstStatement', () => {
end sub
`);
});

it('transpiles nested consts that reference other consts within same namespace', () => {
testTranspile(`
namespace theming
const FLAG_A = "A"
const FLAG_B = "B"
const AD_BREAK_START = { a: FLAG_A, b: FLAG_B }
end namespace
sub main()
print theming.AD_BREAK_START
end sub
`, `
sub main()
print ({
a: "A"
b: "B"
})
end sub
`);
});

it('transpiles nested consts that reference other consts in different namespaces', () => {
testTranspile(`
namespace aa.bb
const FLAG_A = "A"
end namespace
namespace main
const FLAG_B = "B"
const AD_BREAK_START = { a: aa.bb.FLAG_A, b: FLAG_B }
end namespace
sub main()
print main.AD_BREAK_START
end sub
`, `
sub main()
print ({
a: "A"
b: "B"
})
end sub
`);
});

it('transpiles nested consts that reference other consts across files', () => {
program.setFile('source/constants.bs', `
namespace theming
const PRIMARY_COLOR = "blue"
end namespace
const FLAG_B = "B"
`);
testTranspile(`
const SECONDARY_COLOR = theming.PRIMARY_COLOR
const AD_BREAK_START = { a: SECONDARY_COLOR, b: FLAG_B }
sub main()
print AD_BREAK_START
end sub
`, `
sub main()
print ({
a: "blue"
b: "B"
})
end sub
`);
});

it('recursively resolves nested consts that reference other consts', () => {
testTranspile(`
const FLAG_A = "A"
const FLAG_B = FLAG_A
const AD_BREAK_START = { a: FLAG_A, b: FLAG_B }
sub main()
print AD_BREAK_START
end sub
`, `
sub main()
print ({
a: "A"
b: "A"
})
end sub
`);
});

it('handles the exact example from the issue - nested consts with namespace references', () => {
testTranspile(`
namespace aa.bb
const FLAG_A = "test"
end namespace
const FLAG_B = "another"
const AD_BREAK_START = { a: aa.bb.FLAG_A, b: FLAG_B }
sub main()
print AD_BREAK_START
end sub
`, `
sub main()
print ({
a: "test"
b: "another"
})
end sub
`);
});
});

describe('completions', () => {
Expand Down
Loading