Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
4dac044
WIP: adding pom.xml validator to the extension
rodinaarssen Jul 31, 2026
8de5482
Launch REPL for open Rascal project.
toinehartman Aug 4, 2026
2d0f50d
Work around nullability analysis.
toinehartman Aug 5, 2026
2b9a392
Fix Maven resolution.
toinehartman Aug 5, 2026
8a99601
Merge branch 'feature/remove-std-scheme' into feature/add-pomxml-vali…
toinehartman Aug 6, 2026
0024640
Merge remote-tracking branch 'origin/feature/remove-std-scheme' into …
toinehartman Aug 7, 2026
3dbbc5e
WIP: Add pom analyzer in Rascal.
toinehartman Aug 24, 2026
37968f2
Improvements.
toinehartman Aug 25, 2026
a755a31
Merge remote-tracking branch 'origin/main' into feature/add-pomxml-va…
rodinaarssen Sep 4, 2026
55fca31
Merge remote-tracking branch 'refs/remotes/origin/feature/add-pomxml-…
rodinaarssen Sep 4, 2026
b3a8714
PomValidator progress
rodinaarssen Sep 10, 2026
e62939f
Added cache for pom.xml files
rodinaarssen Sep 14, 2026
b4ec870
Added missing license
rodinaarssen Sep 14, 2026
6754d06
Added keyword parameters for the versions
rodinaarssen Sep 14, 2026
39ada87
Made pom analyzer more robust
rodinaarssen Sep 14, 2026
d03ec97
Added support for exclusions
rodinaarssen Sep 14, 2026
30026f5
Wired up pom analyzer in the global analyzer
rodinaarssen Sep 14, 2026
bf82636
Added Rascal dependency check to analyzer
rodinaarssen Sep 14, 2026
bf215fd
Improved rascal-lsp dependency check
rodinaarssen Sep 14, 2026
dbb6033
Removed TS implementation of the pom.xml analyzer
rodinaarssen Sep 14, 2026
a49f1b9
Added comment
rodinaarssen Sep 14, 2026
dc4c458
Removed reference to removed file
rodinaarssen Sep 14, 2026
a61a968
Removed code action that no longer exists
rodinaarssen Sep 14, 2026
faaa5d7
Merge remote-tracking branch 'origin/main' into feature/add-pomxml-va…
rodinaarssen Sep 15, 2026
2c651a8
Newline detection is now robust against single-line pom.xml files
rodinaarssen Sep 15, 2026
386eaf9
Added Java functions to obtain Maven dependencies
rodinaarssen Sep 15, 2026
25250ed
Rewrote addDependency TextEdit generation to work on plain nodes
rodinaarssen Sep 15, 2026
65793a5
Cleanup of PomAnalyzer.rsc
rodinaarssen Sep 15, 2026
8c82320
Analyzer now uses proper PomAnalyzer functions
rodinaarssen Sep 15, 2026
65e98dd
Merge branch 'main' into feature/add-pomxml-validator
rodinaarssen Sep 15, 2026
4ef3048
Using SpecificationVersion first to find the active rascal-lsp version
rodinaarssen Sep 15, 2026
9415c2d
Added Rascal commands in favor of plain text edits
rodinaarssen Sep 15, 2026
7b7ba65
Removed pom.xml check for a Rascal dependency; this is moved to Rascal
rodinaarssen Sep 15, 2026
4848d6f
Added custom code action provider for xml files to be able to provide…
rodinaarssen Sep 21, 2026
f7fd701
Made Rascal version check robust against running from a target folder
rodinaarssen Sep 21, 2026
4285706
Always read full pom.xml files
rodinaarssen Sep 21, 2026
f19683d
Correctly added argument to command
rodinaarssen Sep 21, 2026
c62b360
RascalTextDocumentService also handles xml files now
rodinaarssen Sep 21, 2026
73ad8d5
Fixed typo
rodinaarssen Sep 21, 2026
01aafa0
Extracted code action conversion function
rodinaarssen Sep 21, 2026
1d4f791
Prevent RascalTextDocumentService from trying to parse pom.xml files …
rodinaarssen Sep 21, 2026
d5701cb
Fixed ShellEvaluatorFactory call after a formal argument got removed …
rodinaarssen Sep 21, 2026
9a72d44
Fixed off-by-one error and added an overload for inferNewline in the …
rodinaarssen Sep 22, 2026
bc37a5b
Added tests for the pom.xml analyzer
rodinaarssen Sep 22, 2026
44ca5de
Slightly simplified inferIndentation
rodinaarssen Sep 22, 2026
e933c47
Removed unused code
rodinaarssen Sep 22, 2026
75f44a8
Unexported a variable
rodinaarssen Sep 22, 2026
5fc6530
Only provide code actions for pom.xml files instead of for all xml files
rodinaarssen Sep 22, 2026
b5c0cba
Using latest RC of Rascal
rodinaarssen Sep 22, 2026
6e2ec42
Removed unused imports
rodinaarssen Sep 22, 2026
f8ad534
Fixed CF error
rodinaarssen Sep 22, 2026
71c59be
Incorporated feedback by @DavyLandman
rodinaarssen Sep 22, 2026
b549a59
Moved code action provider for pom.xml files to a discrete class
rodinaarssen Sep 22, 2026
828b681
Got rid of fixed back-up versions for rascal and rascal-lsp in the code
rodinaarssen Sep 22, 2026
6dcdf51
Unused import removed
rodinaarssen Sep 22, 2026
0dac9f0
Changed log level
rodinaarssen Sep 22, 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
6 changes: 6 additions & 0 deletions rascal-lsp/src/main/rascal/lsp/lang/rascal/lsp/Actions.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ data Command
= visualImportGraphCommand(PathConfig pcfg)
| sortImportsAndExtends(Header h)
| upgradeAnnotations(PathConfig pcfg)
| addRascalToPom(loc l)
;


Expand Down Expand Up @@ -165,3 +166,8 @@ value evaluateRascalCommand(upgradeAnnotations(PathConfig pcfg)) {
return ("result":true);
}

value evaluateRascalCommand(addRascalToPom(loc l)) {

return ("result": true);
}

123 changes: 123 additions & 0 deletions rascal-lsp/src/main/rascal/lsp/lang/xml/PomAnalyzer.rsc
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
module lang::xml::PomAnalyzer

import lang::xml::DOM;
import lang::xml::IO;
import analysis::diff::edits::ExecuteTextEdits;
import analysis::diff::edits::TextEdits;

import IO;
import List;
import Node;
import String;
import util::IDEServices;
import util::Maybe;

bool(node) hasProperty(str tagName, str tagValue) {
return bool(node \node) {
return "<tagName>"(tagValue) <- getChildren(\node);
};
}

Maybe[&T](node) getProperty(type[&T] _, str tagName) {
return Maybe[&T](node \node) {
if ("<tagName>"(&T tagValue) <- getChildren(\node)) {
return just(tagValue);
}
return nothing();
};
}

node getChildNode(node n, str name) {
if (node child <- getChildren(n), name := getName(child)) {
return child;
}
throw "No child with name \'<name>\' in \'<getName(n)>\': <[getName(c) | node c <- getChildren(n)]>";
}

bool(node) hasRascalMplGroup = hasProperty("groupid", "org.rascalmpl");

bool(node) isRascalArtifact = hasProperty("artifactid", "rascal");

bool(node) isRascalLspArtifact = hasProperty("artifactid", "rascal-lsp");

Maybe[node] getRascalDependency(list[node] dependencies) {
if (dep <- dependencies, hasRascalMplGroup(dep), isRascalArtifact(dep)) {
return just(dep);
}
return nothing();
}

Maybe[node] getRascalLspDependency(list[node] dependencies) {
if (dep <- dependencies, hasRascalMplGroup(dep), isRascalLspArtifact(dep)) {
return just(dep);
}
return nothing();
}

Maybe[str](node) getVersion = getProperty(#str, "version");

str inferRascalVersion(Maybe[str] rascalLsp = nothing()) {
if (just(str lspVersion) := rascalLsp) {
// Figure out a compatible version
return "0.43.0";
}

// Read Rascal version from LSP dependencies
lspPom = |project://rascal-lsp/pom.xml|;
if (exists(lspPom), /"dependency"("groupid"("org.rascalmpl"), "artifactid"("rascal"), "version"(str rascalVersion)) := readXML(lspPom)) {
return rascalVersion;
}

xml = readXML(lspPom);
iprintln(xml);
return "0.43.0";
}

node dependencyTemplate(str artifactId, str version, str groupId="org.rascalmpl")
= "dependency"(
"groupId"(groupId),
"artifactId"(artifactId),
"version"(version)
);

node dependenciesTemplate(node dependencies...)
= makeNode("dependencies", *dependencies);

str prettyXML(node xml) {
prettied = xmlPretty(toXML(xml));
// `xmlPretty` assumes the node is a document and thus adds an `<?xml ...?>` tag at the start
return intercalate("\n", split("\n", prettied)[1..]);
}

void main(loc pomLoc = |project://rascal-vscode-extension/test-workspace/test-project/pom.xml|) {
if (node pom := readXML(pomLoc, trackOrigins = true)) {
// list[TextEdit] edits = [];
if (project := getChildNode(pom, "project")) {
if (dependenciesBlock := getChildNode(project, "dependencies")) {
dependencies = getChildren(dependenciesBlock);
if (just(rascal) := getRascalDependency(dependencies)) {
println("Found Rascal dependency: <getVersion(rascal)>");
// Check minimal version
;
} else {
// No Rascal dependency; offer to add one
println("No Rascal dependency found!");
rascalLsp = getRascalLspDependency(dependencies);
rascalVersion = inferRascalVersion(rascalLsp=getVersion(rascalLsp));
insertionPoint = getFirstFrom(getChildren(dependenciesBlock));
rascalEdit = insertAfter(dependenciesBlock.src, prettyXML(dependencyTemplate("rascal", rascalVersion)));
iprintln(rascalEdit);
applyFileSystemEdits([changed([rascalEdit])]);
}
} else {
println("No dependencies block found");
rascalEdit = insertAfter(getChildNode(project, "version").src, xmlPretty(toXML(dependenciesTemplate(dependencyTemplate("rascal", inferRascalVersion())))));
iprintln(rascalEdit);
applyFileSystemEdits([changed([rascalEdit])]);
}
} else {
println("Unrecoverably broken POM!");
; // This a very sparse POM. We can probably not recover from this.
}
}
}
2 changes: 2 additions & 0 deletions rascal-vscode-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { RascalExtension } from './RascalExtension';
import { RascalMFValidator } from './ux/RascalMFValidator';
import { RascalProjectValidator } from './ux/RascalProjectValidator';
import { VsCodeSettingsFixer } from './ux/VsCodeSettingsFixer';
import { PomXmlValidator } from './ux/PomXmlValidator';

const testDeployMode = (process.env['RASCAL_LSP_DEV_DEPLOY'] || "false") === "true";
const deployMode = (process.env['RASCAL_LSP_DEV'] || "false") !== "true";
Expand All @@ -44,6 +45,7 @@ export function activate(context: vscode.ExtensionContext) {
logger.info(`Starting extension deployMode: ${deployMode} testDeployMode: ${testDeployMode}`);
context.subscriptions.push(extension);
context.subscriptions.push(new RascalMFValidator());
context.subscriptions.push(new PomXmlValidator(logger));
context.subscriptions.push(new VsCodeSettingsFixer());
context.subscriptions.push(new RascalProjectValidator(logger));
if (!deployMode || testDeployMode) {
Expand Down
154 changes: 154 additions & 0 deletions rascal-vscode-extension/src/ux/PomXmlValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright (c) 2018-2025, NWO-I CWI and Swat.engineering
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import * as vscode from 'vscode';
import { isRascalProject, MF_DIR } from './RascalMFValidator';

const POM_XML_FILE = "pom.xml";

export class PomXmlValidator implements vscode.Disposable {
private readonly diagnostics: vscode.DiagnosticCollection;
private readonly disposables: vscode.Disposable[] = [];

constructor (private readonly logger: vscode.LogOutputChannel) {
logger.info("pom.xml validator starting");
this.diagnostics = vscode.languages.createDiagnosticCollection("pom.xml diagnostics");

// new projects should be checked
vscode.workspace.onDidChangeWorkspaceFolders(async ws => {
for (const added of ws.added) {
if (await isRascalProject(added.uri)) {
void this.verifyPomXml(added.uri);
}
}
for (const rem of ws.removed) {
// clear messages of projects that are no longer in the workspace
this.diagnostics.delete(this.buildPomXmlChildPath(rem.uri));
}
}, this, this.disposables);

// check open folders
for (const openProject of vscode.workspace.workspaceFolders || []) {
const pomXmlUri = this.buildPomXmlChildPath(openProject.uri);
void vscode.workspace.fs.stat(pomXmlUri).then(_s => void this.verifyPomXml(pomXmlUri));
}

// watch the file system for changes to pom.xml files
const watcher = vscode.workspace.createFileSystemWatcher("**/" + POM_XML_FILE, true, false, false);
watcher.onDidCreate(this.verifyPomXml, this, this.disposables);
watcher.onDidChange(this.verifyPomXml, this, this.disposables);
watcher.onDidDelete(e => this.diagnostics.delete(e), this, this.disposables);
this.disposables.push(watcher);

this.disposables.push(
vscode.languages.registerCodeActionsProvider(
{ pattern: "**/" + POM_XML_FILE },
new FixPomXmlIssues()
)
);
}

dispose() {
this.safeDispose(this.diagnostics);
for (const d of this.disposables) {
this.safeDispose(d);
}
}

private safeDispose(d: vscode.Disposable): void {
try {
d.dispose();
} catch (_e) { /* ignore errors */ }
}

private async verifyPomXml(file: vscode.Uri) {
try {
const pomXmlBody = await vscode.workspace.openTextDocument(file);
const diagnostics : vscode.Diagnostic[] = [];

try {
checkRascalDependency(pomXmlBody, diagnostics);
} finally {
this.diagnostics.set(file, diagnostics);
}
} catch (_error) {
// Ignore errors
}
}

private buildPomXmlChildPath(uri: vscode.Uri) {
return vscode.Uri.joinPath(uri, MF_DIR, POM_XML_FILE);
}
}

enum FixKind {
noRascalDependency = 1,
outdatedRascalDependency
}

class FixPomXmlIssues implements vscode.CodeActionProvider {
provideCodeActions(_document: vscode.TextDocument, _range: vscode.Range | vscode.Selection, context: vscode.CodeActionContext, _token: vscode.CancellationToken): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> {
const result: vscode.CodeAction[] = [];
for (const diag of context.diagnostics) {
switch (diag.code) {
case FixKind.noRascalDependency: {
const addRascalDependency = new vscode.CodeAction("Add Rascal dependency", vscode.CodeActionKind.Empty);
addRascalDependency.diagnostics = [diag];
addRascalDependency.isPreferred = true;
// addRascalDependency.command = ...
// vscode.window.showErrorMessage("...");
result.push(addRascalDependency);
break;
}
case FixKind.outdatedRascalDependency: {
// TODO
break;
}
}
}
return result;
}
}
const foo = /<!--([^-]*?|-->)/;
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

// Pitfall: this regular expression only accepts groupId, artifactId, and version in that particular order, and does not support comments
const dependencyMatcher = /<dependency>\s*<groupId>([^<]*?)<\/groupId>\s*<artifactId>([^<]*?)<\/artifactId>\s*<version>([^<]*?)<\/version>\s*<\/dependency>/g;

function checkRascalDependency(pomXmlBody: vscode.TextDocument, diagnostics: vscode.Diagnostic[]) {
let match: RegExpExecArray | null;
const pomXmlText = pomXmlBody.getText();
while ((match = dependencyMatcher.exec(pomXmlText))) {
if (match[1] === "org.rascalmpl" && match[2] === "rascal") {
//TODO: check that version is new enough
}
return;
}
const diag = new vscode.Diagnostic(
new vscode.Range(0, 0, 0, 0), "Could not detect a Rascal dependency, please add one to this pom.xml"
);
diag.code = FixKind.noRascalDependency;
diagnostics.push(diag);
}
2 changes: 1 addition & 1 deletion rascal-vscode-extension/src/ux/RascalMFValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {posix} from 'path'; // posix path join is always correct, also on window


export const MF_FILE = "RASCAL.MF";
const MF_DIR = "META-INF";
export const MF_DIR = "META-INF";

/**
* Check for common errors in RASCAL.MF files, and try and fix them if possible
Expand Down
Loading