Refactor large headers and enforce architecture constraints
This commit is contained in:
156
editor/src/diagnostics/StructuredDiagnosticsPart1.h
Normal file
156
editor/src/diagnostics/StructuredDiagnosticsPart1.h
Normal file
@@ -0,0 +1,156 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// Collectors: convert each diagnostic source to unified format
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
inline std::vector<StructuredDiagnostic> collectParseDiagnostics(
|
||||
const std::vector<ParseDiagnostic>& diags) {
|
||||
std::vector<StructuredDiagnostic> out;
|
||||
out.reserve(diags.size());
|
||||
for (const auto& d : diags) {
|
||||
StructuredDiagnostic sd;
|
||||
sd.code = parseErrorCode(d.message);
|
||||
sd.severity = severityFromStr(d.severity);
|
||||
sd.line = d.line;
|
||||
sd.col = d.column;
|
||||
sd.message = d.message;
|
||||
sd.source = "parser";
|
||||
out.push_back(std::move(sd));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::vector<StructuredDiagnostic> collectAnnotationDiagnostics(
|
||||
const std::vector<AnnotationValidator::Diagnostic>& diags) {
|
||||
std::vector<StructuredDiagnostic> out;
|
||||
out.reserve(diags.size());
|
||||
for (const auto& d : diags) {
|
||||
StructuredDiagnostic sd;
|
||||
sd.code = annotationErrorCode(d.message);
|
||||
sd.severity = severityFromStr(d.severity);
|
||||
sd.nodeId = d.nodeId;
|
||||
sd.message = d.message;
|
||||
sd.source = "annotation";
|
||||
sd.fix = buildAnnotationFix(d.nodeId, d.message);
|
||||
out.push_back(std::move(sd));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::vector<StructuredDiagnostic> collectStrategyDiagnostics(
|
||||
const std::vector<StrategyValidator::Violation>& violations) {
|
||||
std::vector<StructuredDiagnostic> out;
|
||||
out.reserve(violations.size());
|
||||
for (const auto& v : violations) {
|
||||
StructuredDiagnostic sd;
|
||||
sd.code = strategyErrorCode(v.category);
|
||||
sd.severity = severityFromStr(v.severity);
|
||||
sd.nodeId = v.nodeId;
|
||||
sd.message = v.message;
|
||||
sd.source = "strategy";
|
||||
sd.fix = buildStrategyFix(v.nodeId, v.category);
|
||||
out.push_back(std::move(sd));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// collectAllDiagnostics — run the full validation pipeline on an AST
|
||||
// -----------------------------------------------------------------------
|
||||
inline std::vector<StructuredDiagnostic> collectAllDiagnostics(
|
||||
Module* ast) {
|
||||
std::vector<StructuredDiagnostic> all;
|
||||
if (!ast) return all;
|
||||
|
||||
// Annotation validation
|
||||
AnnotationValidator annoValidator;
|
||||
auto annoDiags = annoValidator.validate(ast);
|
||||
auto annoStructured = collectAnnotationDiagnostics(annoDiags);
|
||||
all.insert(all.end(), annoStructured.begin(), annoStructured.end());
|
||||
|
||||
// Extended annotation validation (Subjects 2-8)
|
||||
AnnotationValidatorExtended extValidator;
|
||||
auto extDiags = extValidator.validate(ast);
|
||||
auto extStructured = collectAnnotationDiagnostics(extDiags);
|
||||
all.insert(all.end(), extStructured.begin(), extStructured.end());
|
||||
|
||||
// Cross-type annotation conflict detection
|
||||
std::vector<CrossTypeConflict> crossConflicts;
|
||||
collectCrossTypeConflicts(ast, crossConflicts);
|
||||
for (const auto& c : crossConflicts) {
|
||||
StructuredDiagnostic sd;
|
||||
sd.code = "E0210";
|
||||
sd.severity = DiagnosticSeverity::Error;
|
||||
sd.nodeId = c.nodeId;
|
||||
sd.message = c.type1 + " + " + c.type2 + ": " + c.message;
|
||||
sd.source = "annotation";
|
||||
all.push_back(std::move(sd));
|
||||
}
|
||||
|
||||
// Strategy validation (post-optimization invariants)
|
||||
StrategyValidator stratValidator;
|
||||
auto violations = stratValidator.validateInvariants(ast);
|
||||
auto stratStructured = collectStrategyDiagnostics(violations);
|
||||
all.insert(all.end(), stratStructured.begin(), stratStructured.end());
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// collectPipelineDiagnostics — from a PipelineResult
|
||||
// -----------------------------------------------------------------------
|
||||
inline std::vector<StructuredDiagnostic> collectPipelineDiagnostics(
|
||||
const Pipeline::PipelineResult& pr) {
|
||||
std::vector<StructuredDiagnostic> all;
|
||||
|
||||
auto parseDiags = collectParseDiagnostics(pr.parseDiags);
|
||||
all.insert(all.end(), parseDiags.begin(), parseDiags.end());
|
||||
|
||||
auto annoDiags = collectAnnotationDiagnostics(pr.validationDiags);
|
||||
all.insert(all.end(), annoDiags.begin(), annoDiags.end());
|
||||
|
||||
auto stratDiags = collectStrategyDiagnostics(pr.violations);
|
||||
all.insert(all.end(), stratDiags.begin(), stratDiags.end());
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// diagnosticsToJson — convert array to JSON
|
||||
// -----------------------------------------------------------------------
|
||||
inline json diagnosticsToJson(
|
||||
const std::vector<StructuredDiagnostic>& diags) {
|
||||
json arr = json::array();
|
||||
for (const auto& d : diags)
|
||||
arr.push_back(diagnosticToJson(d));
|
||||
return arr;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// filterBySeverity — keep only diagnostics at or above threshold
|
||||
// -----------------------------------------------------------------------
|
||||
inline std::vector<StructuredDiagnostic> filterBySeverity(
|
||||
const std::vector<StructuredDiagnostic>& diags,
|
||||
DiagnosticSeverity maxSeverity) {
|
||||
std::vector<StructuredDiagnostic> out;
|
||||
for (const auto& d : diags) {
|
||||
if (static_cast<int>(d.severity) <=
|
||||
static_cast<int>(maxSeverity)) {
|
||||
out.push_back(d);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// filterBySource — keep only diagnostics from a specific source
|
||||
// -----------------------------------------------------------------------
|
||||
inline std::vector<StructuredDiagnostic> filterBySource(
|
||||
const std::vector<StructuredDiagnostic>& diags,
|
||||
const std::string& source) {
|
||||
std::vector<StructuredDiagnostic> out;
|
||||
for (const auto& d : diags) {
|
||||
if (d.source == source) out.push_back(d);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
190
editor/src/diagnostics/StructuredDiagnosticsPart2.h
Normal file
190
editor/src/diagnostics/StructuredDiagnosticsPart2.h
Normal file
@@ -0,0 +1,190 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// Step 260: Cross-file diagnostics
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Check for undefined imports: modules imported but not open as buffers
|
||||
inline std::vector<StructuredDiagnostic> collectCrossFileDiagnostics(
|
||||
Module* ast, const std::string& filePath,
|
||||
const std::set<std::string>& openModuleNames) {
|
||||
std::vector<StructuredDiagnostic> diags;
|
||||
if (!ast) return diags;
|
||||
for (auto* child : ast->allChildren()) {
|
||||
if (child->conceptType == "Import") {
|
||||
auto* imp = static_cast<const Import*>(child);
|
||||
if (!imp->moduleName.empty() &&
|
||||
openModuleNames.find(imp->moduleName) ==
|
||||
openModuleNames.end()) {
|
||||
StructuredDiagnostic d;
|
||||
d.code = "E0400";
|
||||
d.severity = DiagnosticSeverity::Warning;
|
||||
d.nodeId = imp->id;
|
||||
d.line = imp->hasSpan() ? imp->spanStartLine : 0;
|
||||
d.message = "Imported module '" + imp->moduleName +
|
||||
"' is not open in the project";
|
||||
d.source = "cross-file";
|
||||
diags.push_back(std::move(d));
|
||||
}
|
||||
}
|
||||
}
|
||||
return diags;
|
||||
}
|
||||
|
||||
// Text-based cross-file import check (for parsers without Import nodes)
|
||||
inline std::vector<StructuredDiagnostic> collectCrossFileDiagnosticsFromSource(
|
||||
const std::string& source, const std::string& filePath,
|
||||
const std::set<std::string>& openModuleNames) {
|
||||
std::vector<StructuredDiagnostic> diags;
|
||||
std::istringstream iss(source);
|
||||
std::string line;
|
||||
int lineNum = 0;
|
||||
while (std::getline(iss, line)) {
|
||||
++lineNum;
|
||||
std::string modName;
|
||||
if (line.substr(0, 7) == "import ") {
|
||||
modName = line.substr(7);
|
||||
while (!modName.empty() && modName.back() == ' ')
|
||||
modName.pop_back();
|
||||
size_t comma = modName.find(',');
|
||||
if (comma != std::string::npos)
|
||||
modName = modName.substr(0, comma);
|
||||
} else if (line.substr(0, 5) == "from ") {
|
||||
size_t space = line.find(' ', 5);
|
||||
if (space != std::string::npos)
|
||||
modName = line.substr(5, space - 5);
|
||||
}
|
||||
if (!modName.empty() &&
|
||||
openModuleNames.find(modName) == openModuleNames.end()) {
|
||||
StructuredDiagnostic d;
|
||||
d.code = "E0400";
|
||||
d.severity = DiagnosticSeverity::Warning;
|
||||
d.line = lineNum;
|
||||
d.message = "Imported module '" + modName +
|
||||
"' is not open in the project";
|
||||
d.source = "cross-file";
|
||||
diags.push_back(std::move(d));
|
||||
}
|
||||
}
|
||||
return diags;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Step 251: QuickFix — a concrete mutation an agent can apply
|
||||
// -----------------------------------------------------------------------
|
||||
struct QuickFix {
|
||||
std::string id; // unique fix ID (e.g. "fix-E0200-func_1")
|
||||
std::string diagCode; // diagnostic code this fixes
|
||||
std::string nodeId; // target node
|
||||
std::string description; // human-readable label
|
||||
std::string category; // "remove-annotation", "change-strategy", etc.
|
||||
json mutation; // concrete mutation object for applyMutation
|
||||
};
|
||||
|
||||
inline json quickFixToJson(const QuickFix& f) {
|
||||
return {
|
||||
{"id", f.id},
|
||||
{"diagCode", f.diagCode},
|
||||
{"nodeId", f.nodeId},
|
||||
{"description", f.description},
|
||||
{"category", f.category},
|
||||
{"mutation", f.mutation}
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// getQuickFixes — collect all applicable fixes for a node
|
||||
// -----------------------------------------------------------------------
|
||||
inline std::vector<QuickFix> getQuickFixesForNode(
|
||||
Module* ast, const std::string& targetNodeId) {
|
||||
std::vector<QuickFix> fixes;
|
||||
if (!ast) return fixes;
|
||||
|
||||
auto diags = collectAllDiagnostics(ast);
|
||||
for (const auto& d : diags) {
|
||||
if (!targetNodeId.empty() && d.nodeId != targetNodeId) continue;
|
||||
if (d.fix.is_null()) continue;
|
||||
|
||||
QuickFix fix;
|
||||
fix.id = "fix-" + d.code + "-" + d.nodeId;
|
||||
fix.diagCode = d.code;
|
||||
fix.nodeId = d.nodeId;
|
||||
fix.description = d.fix.value("description", "Fix " + d.code);
|
||||
fix.mutation = d.fix;
|
||||
|
||||
// Categorize based on error code
|
||||
if (d.code == "E0200") fix.category = "remove-annotation";
|
||||
else if (d.code == "E0201") fix.category = "change-strategy";
|
||||
else if (d.code == "E0202") fix.category = "resolve-conflict";
|
||||
else if (d.code == "E0300") fix.category = "remove-use-after-free";
|
||||
else if (d.code == "E0301") fix.category = "add-deallocation";
|
||||
else fix.category = "general";
|
||||
|
||||
fixes.push_back(std::move(fix));
|
||||
}
|
||||
|
||||
// Additional heuristic fixes from AST inspection
|
||||
ASTNode* node = findNodeById(ast, targetNodeId);
|
||||
if (node) {
|
||||
// Unused variable: if a Variable has no references outside its
|
||||
// own declaration, suggest removal
|
||||
if (node->conceptType == "Variable") {
|
||||
QuickFix fix;
|
||||
fix.id = "fix-unused-" + targetNodeId;
|
||||
fix.diagCode = "E0203";
|
||||
fix.nodeId = targetNodeId;
|
||||
fix.description = "Remove unused variable";
|
||||
fix.category = "remove-unused";
|
||||
fix.mutation = {{"type", "deleteNode"},
|
||||
{"nodeId", targetNodeId}};
|
||||
fixes.push_back(std::move(fix));
|
||||
}
|
||||
|
||||
// Missing return: if a Function body ends without a Return,
|
||||
// suggest adding one
|
||||
if (node->conceptType == "Function") {
|
||||
auto body = node->getChildren("body");
|
||||
bool hasReturn = false;
|
||||
for (auto* stmt : body) {
|
||||
if (stmt->conceptType == "ReturnStatement")
|
||||
hasReturn = true;
|
||||
}
|
||||
if (!hasReturn && !body.empty()) {
|
||||
QuickFix fix;
|
||||
fix.id = "fix-missing-return-" + targetNodeId;
|
||||
fix.diagCode = "E0203";
|
||||
fix.nodeId = targetNodeId;
|
||||
fix.description = "Add missing return statement";
|
||||
fix.category = "missing-return";
|
||||
fix.mutation = {
|
||||
{"type", "insertNode"},
|
||||
{"parentId", targetNodeId},
|
||||
{"role", "body"},
|
||||
{"node", {{"conceptType", "ReturnStatement"},
|
||||
{"children", json::object()}}}
|
||||
};
|
||||
fixes.push_back(std::move(fix));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fixes;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// getQuickFixesAll — collect all fixes for all diagnostics in the AST
|
||||
// -----------------------------------------------------------------------
|
||||
inline std::vector<QuickFix> getQuickFixesAll(Module* ast) {
|
||||
return getQuickFixesForNode(ast, "");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// findQuickFix — look up a specific fix by diagnostic code + nodeId
|
||||
// -----------------------------------------------------------------------
|
||||
inline QuickFix findQuickFix(Module* ast, const std::string& diagCode,
|
||||
const std::string& nodeId) {
|
||||
auto fixes = getQuickFixesForNode(ast, nodeId);
|
||||
for (const auto& f : fixes) {
|
||||
if (f.diagCode == diagCode) return f;
|
||||
}
|
||||
return {}; // empty fix (mutation will be null)
|
||||
}
|
||||
|
||||
111
editor/src/diagnostics/StructuredDiagnosticsPart3.h
Normal file
111
editor/src/diagnostics/StructuredDiagnosticsPart3.h
Normal file
@@ -0,0 +1,111 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// Step 252: DiagnosticVersionTracker — track diagnostic changes
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Key that identifies a unique diagnostic instance
|
||||
struct DiagnosticKey {
|
||||
std::string code;
|
||||
std::string nodeId;
|
||||
int line = 0;
|
||||
|
||||
bool operator==(const DiagnosticKey& o) const {
|
||||
return code == o.code && nodeId == o.nodeId && line == o.line;
|
||||
}
|
||||
bool operator<(const DiagnosticKey& o) const {
|
||||
if (code != o.code) return code < o.code;
|
||||
if (nodeId != o.nodeId) return nodeId < o.nodeId;
|
||||
return line < o.line;
|
||||
}
|
||||
};
|
||||
|
||||
inline DiagnosticKey keyFromDiagnostic(const StructuredDiagnostic& d) {
|
||||
return {d.code, d.nodeId, d.line};
|
||||
}
|
||||
|
||||
struct DiagnosticDelta {
|
||||
std::vector<StructuredDiagnostic> added;
|
||||
std::vector<StructuredDiagnostic> removed;
|
||||
int version = 0;
|
||||
};
|
||||
|
||||
class DiagnosticVersionTracker {
|
||||
public:
|
||||
int version = 0;
|
||||
|
||||
// Record current diagnostics snapshot and bump version
|
||||
void recordSnapshot(const std::vector<StructuredDiagnostic>& diags) {
|
||||
previousDiags_ = currentDiags_;
|
||||
previousVersion_ = version;
|
||||
currentDiags_ = diags;
|
||||
++version;
|
||||
}
|
||||
|
||||
// Compute delta: what changed since the given version
|
||||
DiagnosticDelta getDelta(
|
||||
const std::vector<StructuredDiagnostic>& currentDiags,
|
||||
int sinceVersion) const {
|
||||
DiagnosticDelta delta;
|
||||
delta.version = version;
|
||||
|
||||
if (sinceVersion >= version) {
|
||||
// No changes since that version
|
||||
return delta;
|
||||
}
|
||||
|
||||
// Build key sets for previous and current
|
||||
std::set<DiagnosticKey> prevKeys;
|
||||
std::map<DiagnosticKey, StructuredDiagnostic> prevMap;
|
||||
for (const auto& d : previousDiags_) {
|
||||
auto key = keyFromDiagnostic(d);
|
||||
prevKeys.insert(key);
|
||||
prevMap[key] = d;
|
||||
}
|
||||
|
||||
std::set<DiagnosticKey> currKeys;
|
||||
std::map<DiagnosticKey, StructuredDiagnostic> currMap;
|
||||
for (const auto& d : currentDiags) {
|
||||
auto key = keyFromDiagnostic(d);
|
||||
currKeys.insert(key);
|
||||
currMap[key] = d;
|
||||
}
|
||||
|
||||
// Added: in current but not in previous
|
||||
for (const auto& key : currKeys) {
|
||||
if (prevKeys.find(key) == prevKeys.end()) {
|
||||
delta.added.push_back(currMap[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// Removed: in previous but not in current
|
||||
for (const auto& key : prevKeys) {
|
||||
if (currKeys.find(key) == currKeys.end()) {
|
||||
delta.removed.push_back(prevMap[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
// Reset tracker
|
||||
void reset() {
|
||||
version = 0;
|
||||
previousVersion_ = 0;
|
||||
currentDiags_.clear();
|
||||
previousDiags_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
int previousVersion_ = 0;
|
||||
std::vector<StructuredDiagnostic> currentDiags_;
|
||||
std::vector<StructuredDiagnostic> previousDiags_;
|
||||
};
|
||||
|
||||
inline json deltaToJson(const DiagnosticDelta& delta) {
|
||||
return {
|
||||
{"added", diagnosticsToJson(delta.added)},
|
||||
{"removed", diagnosticsToJson(delta.removed)},
|
||||
{"addedCount", (int)delta.added.size()},
|
||||
{"removedCount", (int)delta.removed.size()},
|
||||
{"version", delta.version}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user