Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fbf7221
first pass at cdata parsing and passing down the program
chrisdp Mar 28, 2026
8203e57
fixed where errors are reported
chrisdp Mar 28, 2026
e9bfe25
fixes for ranges and code actions
chrisdp Mar 28, 2026
e74d0b0
mechanism to remap lsp actions for cdata files
chrisdp Mar 28, 2026
e5d28fb
add LSP event remapping tests for CDATA blocks
chrisdp Mar 28, 2026
b968477
test fixes for windows
chrisdp Mar 28, 2026
6eb27b3
testing more fixes for windows
chrisdp Mar 28, 2026
5b16231
Use offset-padded synthetic files to eliminate CDATA coordinate remap…
chrisdp Mar 30, 2026
7aa4b1e
Transpile CDATA blocks back into XML instead of extracting to separat…
chrisdp Mar 30, 2026
e69fd5f
Move CDATA synthetic file metadata from Program map onto BrsFile
chrisdp Mar 30, 2026
761d155
Replace eager CDATA pre-transpile loop with lazy cdataTranspile callback
chrisdp Mar 30, 2026
8787dd9
added new excludeFromOutput prop to BscFile
chrisdp Mar 30, 2026
b2ecc4c
Restore _cdataDiagnosticsContext and cover all synthetic file plugin …
chrisdp Mar 30, 2026
a8d8803
Fix CDATA remapping gaps in workspace symbols and afterFileParse timing
chrisdp Mar 30, 2026
123813b
Enhance CDATA handling by adding parse options for accurate position …
chrisdp Mar 30, 2026
99f3d8f
unit tests
chrisdp Mar 30, 2026
67f55dc
Code review improvements: docs, cdataText regex, and setFileInternal …
chrisdp Mar 30, 2026
fefaa1b
Remove dual-content representation for synthetic CDATA BrsFiles
chrisdp Mar 31, 2026
5cba030
lintting
chrisdp Mar 31, 2026
9a1249f
Fix lint: use RegExp#exec() instead of String#match()
chrisdp Mar 31, 2026
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
430 changes: 372 additions & 58 deletions src/Program.ts

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/ProgramBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ export class ProgramBuilder {
throw new Error('Internal invariant exception: somehow file watcher ran before `ProgramBuilder.run()`');
}
thePath = s`${path.resolve(this.rootDir, thePath)}`;
//ignore file events for synthetic files (e.g. extracted CDATA scripts) — they are managed
//programmatically by XmlFile and don't exist on disk in the source directory
if (this.program.getFile(thePath)?.isSynthetic) {
return;
}
if (event === 'add' || event === 'change') {
const fileObj = {
src: thePath,
Expand Down
6 changes: 5 additions & 1 deletion src/bscPlugin/SignatureHelpUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ export class SignatureHelpUtil {

const documentation = functionComments.join('').trim();

const lines = util.splitIntoLines(file.fileContents);
// Synthetic BrsFiles (inline CDATA blocks) store only the raw CDATA text, so their
// fileContents cannot be indexed by XML-space line numbers. Use the parent XML file's
// contents instead — it has the correct text at every XML-coordinate line number.
const contentsForLines = file.parentXmlFile?.fileContents ?? file.fileContents;
const lines = util.splitIntoLines(contentsForLines);
let key = statement.name.text + documentation;
const params = [] as ParameterInformation[];
for (const param of func.parameters) {
Expand Down
23 changes: 19 additions & 4 deletions src/bscPlugin/symbols/WorkspaceSymbolProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { isBrsFile } from '../../astUtils/reflection';
import { isBrsFile, isXmlFile } from '../../astUtils/reflection';
import type { BrsFile } from '../../files/BrsFile';
import type { XmlFile } from '../../files/XmlFile';
import type { ProvideWorkspaceSymbolsEvent } from '../../interfaces';
import { getWorkspaceSymbolsFromBrsFile } from './symbolUtils';
import util from '../../util';

export class WorkspaceSymbolProcessor {
public constructor(
Expand All @@ -12,17 +14,30 @@ export class WorkspaceSymbolProcessor {

public process() {
const results = Object.values(this.event.program.files).map(file => {
if (isBrsFile(file)) {
if (isBrsFile(file) && !file.isSynthetic) {
return this.getBrsFileWorkspaceSymbols(file);
} else if (isXmlFile(file)) {
return this.getXmlFileWorkspaceSymbols(file);
}
return [];
});
return results.flat();
}

private getBrsFileWorkspaceSymbols(file: BrsFile) {
const symbols = getWorkspaceSymbolsFromBrsFile(file);
private getBrsFileWorkspaceSymbols(file: BrsFile, uriOverride?: string) {
const symbols = getWorkspaceSymbolsFromBrsFile(file, uriOverride);
this.event.workspaceSymbols.push(...symbols);
return this.event.workspaceSymbols;
}

private getXmlFileWorkspaceSymbols(file: XmlFile) {
const xmlUri = util.pathToUri(file.srcPath);
for (const pkgPath of file.inlineScriptPkgPaths) {
const brsFile = this.event.program.getFile<BrsFile>(pkgPath);
if (brsFile) {
this.getBrsFileWorkspaceSymbols(brsFile, xmlUri);
}
}
return this.event.workspaceSymbols;
}
}
4 changes: 2 additions & 2 deletions src/bscPlugin/symbols/symbolUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ export function getDocumentSymbolsFromBrsFile(file: BrsFile) {
}
}

export function getWorkspaceSymbolsFromBrsFile(file: BrsFile) {
export function getWorkspaceSymbolsFromBrsFile(file: BrsFile, uriOverride?: string) {
const result: WorkspaceSymbol[] = [];
const uri = util.pathToUri(file.srcPath);
const uri = uriOverride ?? util.pathToUri(file.srcPath);
let symbolsToProcess = getSymbolsFromAstNode(file.ast);
while (symbolsToProcess.length > 0) {
//get the symbol
Expand Down
36 changes: 33 additions & 3 deletions src/files/BrsFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { Parser, ParseMode } from '../parser/Parser';
import type { FunctionExpression, VariableExpression } from '../parser/Expression';
import type { ClassStatement, NamespaceStatement, AssignmentStatement, MethodStatement, FieldStatement } from '../parser/Statement';
import type { Program } from '../Program';
import type { XmlFile } from './XmlFile';
import type { SGScript } from '../parser/SGTypes';
import { DynamicType } from '../types/DynamicType';
import { FunctionType } from '../types/FunctionType';
import { VoidType } from '../types/VoidType';
Expand Down Expand Up @@ -79,6 +81,28 @@ export class BrsFile {
this.srcPath = value;
}

/**
* When true, this file was generated from inline CDATA content in an XML file and is not backed by a file on disk.
* The file watcher should ignore change events for paths that match a synthetic file.
*/
public isSynthetic = false;

/**
* The XML file this synthetic BrsFile was extracted from. Only set when `isSynthetic` is true.
*/
public parentXmlFile: XmlFile | undefined;

/**
* The SGScript AST node whose CDATA block this file was extracted from. Only set when `isSynthetic` is true.
* Provides access to the CDATA range (`cdataScript.cdata.range`), raw text, and script type.
*/
public cdataScript: SGScript | undefined;

/**
* When true, this file will not be written as a standalone output file during transpile.
*/
public excludeFromOutput = false;

/**
* Will this file result in only comment or whitespace output? If so, it can be excluded from the output if that bsconfig setting is enabled.
*/
Expand Down Expand Up @@ -309,9 +333,13 @@ export class BrsFile {

/**
* Calculate the AST for this file
* @param fileContents the raw source code to parse
* @param fileContents the raw source to parse and store
* @param options optional scan options forwarded to the lexer
* @param options.startLine the zero-indexed line to start position tracking from (used for
* inline CDATA fragments so token ranges are in parent XML coordinate space)
* @param options.startCharacter the zero-indexed character offset on the first line
*/
public parse(fileContents: string) {
public parse(fileContents: string, options?: { startLine?: number; startCharacter?: number }) {
try {
this.fileContents = fileContents;
this.diagnostics = [];
Expand All @@ -326,7 +354,9 @@ export class BrsFile {
//tokenize the input file
let lexer = this.program.logger.time('debug', ['lexer.lex', chalk.green(this.srcPath)], () => {
return Lexer.scan(fileContents, {
includeWhitespace: false
includeWhitespace: false,
startLine: options?.startLine,
startCharacter: options?.startCharacter
});
});

Expand Down
Loading