我可以想象这是不好的做法,但这纯粹是为了一个私人项目以及作为我自己的练习。
我有一个类,它有很多不同的静态方法。它们用于测试一些有关 Java 的东西(我仍在学习 Java,所以我在那里放了不同的东西,我想测试自己,比如继承如何工作等)。
其中一些方法会生成编译时错误,因此我的项目将无法构建。我想创建一个像
@DoNotCompile
这样的注释,它将标记该方法,并且在编译之前该方法将被替换为空主体或类似throw new RuntimeError("This method should not be called")
之类的东西。
目前我只有
@Test
表示将要执行的方法,@NotTest
表示不应该执行的方法,然后我将正文放在注释中。我的目标是学习 - 我想看看我的编辑器中会产生哪些错误,但在编译时忽略它。
如果有帮助的话,我正在 IntelliJ IDEA 2023.1 社区版上使用 Java 17 进行编码。 我使用 gradle,但我对此经验不是很丰富,但也许有一些 gradle 脚本可以实现这一点?
感谢任何帮助,并再次:我知道这是不好的做法,我只是很好奇是否可以实现。
编辑:如果我能以某种方式记录此过程中的编译时错误,并且可能用类似
sout("Did not compile: <errors or warnings from compiler>");
之类的内容替换正文,那就太好了
编辑2:最后我只是编写了一个用于预处理的node.js脚本,并将其作为外部工具添加到我在IntelliJ中的运行配置中。此外,我删除了正常的构建命令,并将类路径替换为我处理的文件路径。现在工作起来就像一个魅力,但它有一些缺点:我只在运行代码时测试特定的注释,所以它不灵活。另外,我不使用解析器来获取该方法,因此它只是假设保留代码样式。我将很快上传脚本作为答案。
作为一种解决方法,我只能创建一个脏预处理器,以下是您需要的设置: (注意:我使用的是 IntelliJ,如果您使用其他 IDE,这可能会有所不同)
const fs = require("fs");
const path = require("path");
const { exec } = require("child_process");
function *fromDir(startPath, filter) {
if (!fs.existsSync(startPath)) {
console.log("no dir", startPath);
return;
}
const files = fs.readdirSync(startPath);
for (let i = 0; i < files.length; i++) {
const filename = path.join(startPath, files[i]);
const stat = fs.lstatSync(filename);
if (stat.isDirectory()) {
for (const f of fromDir(filename, filter)) {
yield f;
}
} else if (filename.endsWith(filter)) {
yield filename;
};
};
};
(async () => {
const classpath = process.argv[2] || "";
const files = [...fromDir("src", ".java")];
const output = await new Promise(res => {
exec("javac -cp \"" + classpath + "\" -d tmp " + files.join(" "), (err, out, serr) => res(serr));
});
const errors = output.trim().split("\r\n").filter(line => files.some(file => line.includes(file)));
if (fs.existsSync("processed")) {
fs.rmSync("processed", { "recursive": true, "force": true });
}
fs.mkdirSync("processed");
fs.cpSync("src", "processed/src", { "recursive": true });
for (const file of files) {
// contains errors then process
const ferr = errors.filter(error => error.includes(file));
if (ferr.length) {
const content = fs.readFileSync(file, "utf8").split(/\r?\n/);
const methods = new Set();
const lookup = { };
for (const error of ferr) {
const line = parseInt(error.slice(file.length + 1).split(":")[0]);
const issue = error.split("error: ").slice(1).join("error: ");
for (let i = line;; i --) {
if (content[i].trim().endsWith("@Test")) {
methods.add(i + 1);
if (!((i+1) in lookup)) lookup[i + 1] = [];
lookup[i + 1].push(issue);
break;
}
}
}
for (const method of methods) {
let search = true;
let tabs;
for (let i = method;; i ++) {
if (search) {
if (content[i].endsWith("{")) search = false;
tabs = content[i].match(/^[ ]*/)[0].length;
const issues = lookup[method].join("\\n").replaceAll("\"", "\\\"");
content[i] = content[i] + " throw new RuntimeException(\"Compile time error was encountered:\\n" + issues + "\");"
} else {
const start = content[i].match(/^[ ]*/)[0];
if (content[i].endsWith("}") && start.length == tabs) break;
if (!content[i].length) continue;
content[i] = start + "//" + content[i].slice(start.length);
}
}
}
fs.writeFileSync(path.join("processed", file), content.join("\n"));
}
}
if (fs.existsSync("tmp")) {
fs.rmSync("tmp", { "recursive": true, "force": true });
}
await new Promise(res => {
exec("javac -cp \"" + classpath + "\" -d processed/out " + files.map(file => path.join("processed", file)).join(" "), (err, out, serr) => res(serr));
});
})();
注意事项:
@Test
来查找此类方法,如果你想使用其他注释,你必须自己修改它@Test
注释不必是最后一个,但建议仍将其设为最后一个预处理器的流程:
throw new RuntimeException("Compile time error was encountered: <error(s)>")
processed/src
文件夹processed/out
文件夹