// @ts-check "use strict"; const vscode = require("vscode"); const selector = { language: "wrn" }; const WRN_GLOB = "**/*.wrn"; const EXCLUDE_GLOB = "**/{node_modules,dist,.wrnexus,.git}/**"; function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function identifierAt(document, position) { const range = document.getWordRangeAtPosition(position, /[A-Za-z_$][A-Za-z0-9_$]*/); if (!range) return null; return { name: document.getText(range), range }; } function occurrences(document, name) { const result = []; const pattern = new RegExp(`\\b${escapeRegExp(name)}\\b`, "g"); const source = document.getText(); let match; while ((match = pattern.exec(source)) !== null) { result.push( new vscode.Range( document.positionAt(match.index), document.positionAt(match.index + name.length), ), ); } return result; } function declarationRanges(document, name) { const source = document.getText(); const escaped = escapeRegExp(name); const patterns = [ new RegExp(`\\b(?:page|component|layout)\\s+(${escaped})\\b`, "g"), new RegExp(`\\b(?:global|page)\\s+store\\s+(${escaped})\\b`, "g"), new RegExp(`\\b(?:client|server|shared)?\\s*(?:async\\s+)?function\\s+(${escaped})\\b`, "g"), new RegExp(`\\b(?:client|server|shared)?\\s*state\\s+(${escaped})\\b`, "g"), new RegExp(`\\boutputs\\s*\\{[\\s\\S]*?\\b(${escaped})\\s*\\(`, "g"), new RegExp( `\\b(?:props|state|computed)\\s*\\{[\\s\\S]*?\\b(${escaped})(?:\\?|\\s)*(?=[:=])`, "g", ), new RegExp(`\\bimport(?:\\s+type)?[\\s\\S]*?\\b(${escaped})\\b[\\s\\S]*?\\bfrom\\b`, "g"), ]; const ranges = []; for (const pattern of patterns) { let match; while ((match = pattern.exec(source)) !== null) { const relative = match[0].lastIndexOf(match[1]); const start = match.index + relative; ranges.push( new vscode.Range(document.positionAt(start), document.positionAt(start + name.length)), ); } } return ranges; } async function wrnDocuments() { const uris = await vscode.workspace.findFiles(WRN_GLOB, EXCLUDE_GLOB, 1_000); const open = new Map( vscode.workspace.textDocuments .filter((item) => item.languageId === "wrn") .map((item) => [item.uri.toString(), item]), ); return Promise.all( uris.map((uri) => open.get(uri.toString()) || vscode.workspace.openTextDocument(uri)), ); } async function provideReferences(document, position, context, token) { const symbol = identifierAt(document, position); if (!symbol || token.isCancellationRequested) return []; const locations = []; for (const candidate of await wrnDocuments()) { if (token.isCancellationRequested) return locations; for (const range of occurrences(candidate, symbol.name)) { if ( !context.includeDeclaration && declarationRanges(candidate, symbol.name).some((decl) => decl.isEqual(range)) ) continue; locations.push(new vscode.Location(candidate.uri, range)); } } return locations; } async function prepareRename(document, position) { const symbol = identifierAt(document, position); if (!symbol) throw new Error("Place the cursor on a WRN identifier."); return { range: symbol.range, placeholder: symbol.name }; } async function provideRenameEdits(document, position, newName, token) { if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(newName)) throw new Error("The new WRN name is not a valid identifier."); const symbol = identifierAt(document, position); if (!symbol) return null; const edit = new vscode.WorkspaceEdit(); for (const candidate of await wrnDocuments()) { if (token.isCancellationRequested) return edit; for (const range of occurrences(candidate, symbol.name)) edit.replace(candidate.uri, range, newName); } return edit; } function importRanges(source) { const ranges = []; const pattern = /^import(?:\s+type)?[\s\S]*?(?:;\s*|\n(?=import|\s*(?:page|component|layout|global\s+store|page\s+store)\b))/gm; let match; while ((match = pattern.exec(source)) !== null) ranges.push({ start: match.index, end: match.index + match[0].length, text: match[0].trim() }); return ranges; } function organizeImportsEdit(document) { const source = document.getText(); const imports = importRanges(source); if (imports.length < 2) return null; const ordered = [...imports].sort((a, b) => { const aType = /^import\s+type\b/.test(a.text) ? 0 : 1; const bType = /^import\s+type\b/.test(b.text) ? 0 : 1; return aType - bType || a.text.localeCompare(b.text); }); const replacement = ordered.map((entry) => entry.text.replace(/;$/, "")).join("\n") + "\n"; const start = imports[0].start; const end = imports.at(-1).end; if (source.slice(start, end) === replacement) return null; return vscode.TextEdit.replace( new vscode.Range(document.positionAt(start), document.positionAt(end)), replacement, ); } function provideCodeActions(document, range, context) { const actions = []; const organizeEdit = organizeImportsEdit(document); if (organizeEdit) { const action = new vscode.CodeAction( "Organize WRN imports", vscode.CodeActionKind.SourceOrganizeImports, ); action.edit = new vscode.WorkspaceEdit(); action.edit.set(document.uri, [organizeEdit]); actions.push(action); } for (const diagnostic of context.diagnostics) { if (diagnostic.code === "WRN-OUTPUT-LEGACY-EMIT") { const source = document.getText(diagnostic.range); const match = /\$emit\(\s*["']([A-Za-z_$][\w$]*)["']\s*,?/.exec(source); if (!match) continue; const action = new vscode.CodeAction( `Convert $emit to output.${match[1]}`, vscode.CodeActionKind.QuickFix, ); action.diagnostics = [diagnostic]; action.isPreferred = true; action.edit = new vscode.WorkspaceEdit(); action.edit.replace( document.uri, diagnostic.range, source.replace(/\$emit\(\s*["'][A-Za-z_$][\w$]*["']\s*,?\s*/, `output.${match[1]}(`), ); actions.push(action); } } return actions; } function registerV060LanguageFeatures(context) { context.subscriptions.push( vscode.languages.registerReferenceProvider(selector, { provideReferences }), vscode.languages.registerRenameProvider(selector, { prepareRename, provideRenameEdits }), vscode.languages.registerCodeActionsProvider( selector, { provideCodeActions }, { providedCodeActionKinds: [ vscode.CodeActionKind.QuickFix, vscode.CodeActionKind.SourceOrganizeImports, ], }, ), ); } module.exports = { declarationRanges, importRanges, organizeImportsEdit, registerV060LanguageFeatures, };