Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
38 changes: 3 additions & 35 deletions extension/src/editor/parsers/csharpAppHostParser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import { AppHostResourceParser, ParsedResource, registerParser } from './AppHostResourceParser';
import { findStatementStartLine } from './parserUtils';

/**
* C# AppHost resource parser.
Expand Down Expand Up @@ -34,8 +35,8 @@ class CSharpAppHostParser implements AppHostResourceParser {
const startPos = document.positionAt(matchStart);
const endPos = document.positionAt(matchStart + match[0].length);

// Find the start of the full statement (walk back to previous semicolon, '{', or start of file)
const statementStartLine = this._findStatementStartLine(text, matchStart, document);
// Find the start of the full statement (walk back to previous ';', '{', '}', or start of file)
const statementStartLine = findStatementStartLine(text, matchStart, document);

results.push({
name: resourceName,
Expand All @@ -49,39 +50,6 @@ class CSharpAppHostParser implements AppHostResourceParser {
return results;
}

/**
* Walk backwards from the match position to find the first line of the statement.
* Stops at the previous ';', '{', or start of file, then returns the first non-comment,
* non-blank line after that delimiter.
*/
private _findStatementStartLine(text: string, matchIndex: number, document: vscode.TextDocument): number {
let i = matchIndex - 1;
while (i >= 0) {
const ch = text[i];
if (ch === ';' || ch === '{') {
break;
}
i--;
}
// i is now at the delimiter or -1 (start of file)
// Find the first non-whitespace character after the delimiter
let start = i + 1;
while (start < matchIndex && /\s/.test(text[start])) {
start++;
}
let line = document.positionAt(start).line;
const matchLine = document.positionAt(matchIndex).line;
// Skip lines that are comments (// or /* or * continuation)
while (line < matchLine) {
const lineText = document.lineAt(line).text.trimStart();
if (lineText.startsWith('//') || lineText.startsWith('/*') || lineText.startsWith('*')) {
line++;
} else {
break;
}
}
return line;
}
}

// Self-register on import
Expand Down
36 changes: 3 additions & 33 deletions extension/src/editor/parsers/jsTsAppHostParser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import { AppHostResourceParser, ParsedResource, registerParser } from './AppHostResourceParser';
import { findStatementStartLine } from './parserUtils';

/**
* JavaScript / TypeScript AppHost resource parser.
Expand Down Expand Up @@ -35,8 +36,8 @@ class JsTsAppHostParser implements AppHostResourceParser {
const startPos = document.positionAt(matchStart);
const endPos = document.positionAt(matchStart + match[0].length);

// Find the start of the full statement (walk back to previous ';', '{', or start of file)
const statementStartLine = this._findStatementStartLine(text, matchStart, document);
// Find the start of the full statement (walk back to previous ';', '{', '}', or start of file)
const statementStartLine = findStatementStartLine(text, matchStart, document);

results.push({
name: resourceName,
Expand All @@ -50,37 +51,6 @@ class JsTsAppHostParser implements AppHostResourceParser {
return results;
}

/**
* Walk backwards from the match position to find the first line of the statement.
* Stops at the previous ';', '{', or start of file, then returns the first non-comment,
* non-blank line after that delimiter.
*/
private _findStatementStartLine(text: string, matchIndex: number, document: vscode.TextDocument): number {
let i = matchIndex - 1;
while (i >= 0) {
const ch = text[i];
if (ch === ';' || ch === '{') {
break;
}
i--;
}
let start = i + 1;
while (start < matchIndex && /\s/.test(text[start])) {
start++;
}
let line = document.positionAt(start).line;
const matchLine = document.positionAt(matchIndex).line;
// Skip lines that are comments (// or /* or * continuation)
while (line < matchLine) {
const lineText = document.lineAt(line).text.trimStart();
if (lineText.startsWith('//') || lineText.startsWith('/*') || lineText.startsWith('*')) {
line++;
} else {
break;
}
}
return line;
}
}

// Self-register on import
Expand Down
83 changes: 83 additions & 0 deletions extension/src/editor/parsers/parserUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import * as vscode from 'vscode';

/**
* Walk backwards from the match position to find the first line of the statement.
* Stops at the previous ';', '{', or start of file, then returns the first non-comment,
* non-blank line after that delimiter. When a '}' is encountered, the matched '{...}'
* block is inspected: if preceded by '=>' it is a lambda body within the current fluent
* chain and is skipped; otherwise the '}' is treated as a statement boundary.
*
* Shared by C# and JS/TS AppHost parsers since the statement-boundary rules are
* identical for C-syntax languages.
*/
export function findStatementStartLine(text: string, matchIndex: number, document: vscode.TextDocument): number {
let i = matchIndex - 1;
while (i >= 0) {
const ch = text[i];
if (ch === ';' || ch === '{') {
break;
}
if (ch === '}') {
const openBraceIdx = findMatchingOpenBrace(text, i);
if (openBraceIdx < 0) {
// No matching open brace — treat as delimiter
break;
}
if (isPrecededByArrow(text, openBraceIdx)) {
// Lambda body in the current fluent chain — skip over it
i = openBraceIdx - 1;
continue;
}
// Separate statement block — treat '}' as delimiter
break;
}
i--;
}
// i is now at the delimiter or -1 (start of file)
// Find the first non-whitespace character after the delimiter
let start = i + 1;
while (start < matchIndex && /\s/.test(text[start])) {
start++;
}
let line = document.positionAt(start).line;
const matchLine = document.positionAt(matchIndex).line;
// Skip lines that are only closing braces (with optional comment) or comments
while (line < matchLine) {
const lineText = document.lineAt(line).text.trimStart();
if (/^\}\s*(\/\/.*)?$/.test(lineText) || lineText.startsWith('//') || lineText.startsWith('/*') || lineText.startsWith('*')) {
line++;
} else {
break;
}
}
return line;
}

/**
* Starting from a '}' at closeBraceIdx, walk backwards to find the matching '{'.
* Returns the index of '{', or -1 if not found.
*/
export function findMatchingOpenBrace(text: string, closeBraceIdx: number): number {
let depth = 1;
let j = closeBraceIdx - 1;
while (j >= 0 && depth > 0) {
if (text[j] === '}') {
depth++;
} else if (text[j] === '{') {
depth--;
}
j--;
}
return depth === 0 ? j + 1 : -1;
}

/**
* Check whether the '{' at openBraceIdx is preceded (ignoring whitespace) by '=>'.
*/
export function isPrecededByArrow(text: string, openBraceIdx: number): boolean {
let k = openBraceIdx - 1;
while (k >= 0 && /\s/.test(text[k])) {
k--;
}
return k >= 1 && text[k - 1] === '=' && text[k] === '>';
}
Loading
Loading