229 lines
5.6 KiB
JavaScript
229 lines
5.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Unity Compile Error Analyzer
|
|
*
|
|
* Processes VSCode diagnostics JSON output and extracts Unity-relevant C# compilation errors.
|
|
* Filters by severity (Error only), source (csharp), and classifies errors by type.
|
|
*
|
|
* Usage:
|
|
* node analyze-diagnostics.js <diagnostics-json-file>
|
|
* node analyze-diagnostics.js (reads from stdin)
|
|
*
|
|
* Output: JSON array of classified Unity errors
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Error classification patterns
|
|
const ERROR_CLASSIFICATIONS = {
|
|
MISSING_IMPORT: {
|
|
patterns: [/CS0246/, /type or namespace.*could not be found/i],
|
|
category: 'Missing Import/Namespace',
|
|
priority: 1
|
|
},
|
|
TYPE_MISMATCH: {
|
|
patterns: [/CS0029/, /cannot implicitly convert/i, /cannot convert type/i],
|
|
category: 'Type Mismatch',
|
|
priority: 2
|
|
},
|
|
MEMBER_NOT_FOUND: {
|
|
patterns: [/CS1061/, /does not contain a definition/i],
|
|
category: 'Member Not Found',
|
|
priority: 2
|
|
},
|
|
DUPLICATE_DEFINITION: {
|
|
patterns: [/CS0101/, /already contains a definition/i],
|
|
category: 'Duplicate Definition',
|
|
priority: 3
|
|
},
|
|
SYNTAX_ERROR: {
|
|
patterns: [/CS1002/, /CS1003/, /CS1525/, /expected/i],
|
|
category: 'Syntax Error',
|
|
priority: 1
|
|
},
|
|
ACCESS_MODIFIER: {
|
|
patterns: [/CS0122/, /inaccessible due to its protection level/i],
|
|
category: 'Access Modifier Issue',
|
|
priority: 2
|
|
},
|
|
UNITY_API: {
|
|
patterns: [/MonoBehaviour/, /GameObject/, /Transform/, /Component/],
|
|
category: 'Unity API Issue',
|
|
priority: 1
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Classify error based on message content
|
|
*/
|
|
function classifyError(message) {
|
|
for (const [key, classifier] of Object.entries(ERROR_CLASSIFICATIONS)) {
|
|
for (const pattern of classifier.patterns) {
|
|
if (pattern.test(message)) {
|
|
return {
|
|
type: key,
|
|
category: classifier.category,
|
|
priority: classifier.priority
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
type: 'UNKNOWN',
|
|
category: 'Other Error',
|
|
priority: 4
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Extract error code from message (e.g., "CS0246")
|
|
*/
|
|
function extractErrorCode(message) {
|
|
const match = message.match(/CS\d{4}/);
|
|
return match ? match[0] : null;
|
|
}
|
|
|
|
/**
|
|
* Filter and process diagnostics
|
|
*/
|
|
function analyzeDiagnostics(diagnostics) {
|
|
if (!diagnostics || !Array.isArray(diagnostics)) {
|
|
console.error('Error: Invalid diagnostics format. Expected array.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const unityErrors = diagnostics
|
|
.filter(diag => {
|
|
// Filter: Only errors (not warnings)
|
|
if (diag.severity !== 'Error') return false;
|
|
|
|
// Filter: Only C# errors from OmniSharp
|
|
if (diag.source !== 'csharp') return false;
|
|
|
|
// Filter: Must have valid message
|
|
if (!diag.message) return false;
|
|
|
|
return true;
|
|
})
|
|
.map(diag => {
|
|
const classification = classifyError(diag.message);
|
|
const errorCode = extractErrorCode(diag.message);
|
|
|
|
return {
|
|
file: diag.uri ? diag.uri.replace('file:///', '') : 'unknown',
|
|
line: diag.range?.start?.line ?? 0,
|
|
column: diag.range?.start?.character ?? 0,
|
|
errorCode: errorCode,
|
|
message: diag.message,
|
|
classification: classification.category,
|
|
type: classification.type,
|
|
priority: classification.priority,
|
|
source: diag.source
|
|
};
|
|
});
|
|
|
|
// Sort by priority (lower number = higher priority)
|
|
unityErrors.sort((a, b) => a.priority - b.priority);
|
|
|
|
return unityErrors;
|
|
}
|
|
|
|
/**
|
|
* Generate summary statistics
|
|
*/
|
|
function generateSummary(errors) {
|
|
const summary = {
|
|
totalErrors: errors.length,
|
|
byCategory: {},
|
|
byFile: {},
|
|
highPriority: errors.filter(e => e.priority === 1).length
|
|
};
|
|
|
|
errors.forEach(error => {
|
|
// Count by category
|
|
summary.byCategory[error.classification] =
|
|
(summary.byCategory[error.classification] || 0) + 1;
|
|
|
|
// Count by file
|
|
const fileName = path.basename(error.file);
|
|
summary.byFile[fileName] = (summary.byFile[fileName] || 0) + 1;
|
|
});
|
|
|
|
return summary;
|
|
}
|
|
|
|
/**
|
|
* Main execution
|
|
*/
|
|
function main() {
|
|
const args = process.argv.slice(2);
|
|
|
|
let diagnosticsData;
|
|
|
|
if (args.length === 0) {
|
|
// Read from stdin
|
|
console.error('Reading diagnostics from stdin...');
|
|
const input = fs.readFileSync(0, 'utf-8');
|
|
try {
|
|
diagnosticsData = JSON.parse(input);
|
|
} catch (error) {
|
|
console.error('Error parsing JSON from stdin:', error.message);
|
|
process.exit(1);
|
|
}
|
|
} else {
|
|
// Read from file
|
|
const filePath = args[0];
|
|
|
|
if (!fs.existsSync(filePath)) {
|
|
console.error(`Error: File not found: ${filePath}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
try {
|
|
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
diagnosticsData = JSON.parse(fileContent);
|
|
} catch (error) {
|
|
console.error(`Error reading/parsing file: ${error.message}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Extract diagnostics array (handle different formats)
|
|
let diagnostics;
|
|
if (Array.isArray(diagnosticsData)) {
|
|
diagnostics = diagnosticsData;
|
|
} else if (diagnosticsData.diagnostics && Array.isArray(diagnosticsData.diagnostics)) {
|
|
diagnostics = diagnosticsData.diagnostics;
|
|
} else {
|
|
console.error('Error: Could not find diagnostics array in input');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Analyze
|
|
const errors = analyzeDiagnostics(diagnostics);
|
|
const summary = generateSummary(errors);
|
|
|
|
// Output results
|
|
const output = {
|
|
summary: summary,
|
|
errors: errors
|
|
};
|
|
|
|
console.log(JSON.stringify(output, null, 2));
|
|
}
|
|
|
|
// Run if executed directly
|
|
if (require.main === module) {
|
|
main();
|
|
}
|
|
|
|
// Export for use as module
|
|
module.exports = {
|
|
analyzeDiagnostics,
|
|
classifyError,
|
|
extractErrorCode,
|
|
generateSummary
|
|
};
|