fix: address validation improvements from AI agent feedback
- Filter out @version and internal properties from warnings - Deduplicate validation errors (keep most specific message) - Add basic code syntax validation for JavaScript and Python - Check for unbalanced braces/parentheses - Validate Python indentation consistency - Add n8n-specific pattern checks (return statements, input access) - Bump version to 2.4.2 Based on comprehensive AI agent review showing 9.5/10 rating 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -385,8 +385,10 @@ export class ConfigValidator {
|
||||
message: 'Code cannot be empty',
|
||||
fix: 'Add your code logic'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Security checks
|
||||
if (code?.includes('eval(') || code?.includes('exec(')) {
|
||||
warnings.push({
|
||||
type: 'security',
|
||||
@@ -394,13 +396,23 @@ export class ConfigValidator {
|
||||
suggestion: 'Avoid using eval/exec with untrusted input'
|
||||
});
|
||||
}
|
||||
|
||||
// Basic syntax validation
|
||||
if (config.language === 'python') {
|
||||
this.validatePythonSyntax(code, errors, warnings);
|
||||
} else {
|
||||
this.validateJavaScriptSyntax(code, errors, warnings);
|
||||
}
|
||||
|
||||
// n8n-specific patterns
|
||||
this.validateN8nCodePatterns(code, config.language || 'javascript', warnings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for common configuration issues
|
||||
*/
|
||||
private static checkCommonIssues(
|
||||
nodeType: string,
|
||||
_nodeType: string,
|
||||
config: Record<string, any>,
|
||||
properties: any[],
|
||||
warnings: ValidationWarning[],
|
||||
@@ -411,6 +423,11 @@ export class ConfigValidator {
|
||||
const configuredKeys = Object.keys(config);
|
||||
|
||||
for (const key of configuredKeys) {
|
||||
// Skip internal properties that are always present
|
||||
if (key === '@version' || key.startsWith('_')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!visibleProps.find(p => p.name === key)) {
|
||||
warnings.push({
|
||||
type: 'inefficient',
|
||||
@@ -464,4 +481,129 @@ export class ConfigValidator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic JavaScript syntax validation
|
||||
*/
|
||||
private static validateJavaScriptSyntax(
|
||||
code: string,
|
||||
errors: ValidationError[],
|
||||
warnings: ValidationWarning[]
|
||||
): void {
|
||||
// Check for common syntax errors
|
||||
const openBraces = (code.match(/\{/g) || []).length;
|
||||
const closeBraces = (code.match(/\}/g) || []).length;
|
||||
if (openBraces !== closeBraces) {
|
||||
errors.push({
|
||||
type: 'invalid_value',
|
||||
property: 'jsCode',
|
||||
message: 'Unbalanced braces detected',
|
||||
fix: 'Check that all { have matching }'
|
||||
});
|
||||
}
|
||||
|
||||
const openParens = (code.match(/\(/g) || []).length;
|
||||
const closeParens = (code.match(/\)/g) || []).length;
|
||||
if (openParens !== closeParens) {
|
||||
errors.push({
|
||||
type: 'invalid_value',
|
||||
property: 'jsCode',
|
||||
message: 'Unbalanced parentheses detected',
|
||||
fix: 'Check that all ( have matching )'
|
||||
});
|
||||
}
|
||||
|
||||
// Check for unterminated strings
|
||||
const stringMatches = code.match(/(["'`])(?:(?=(\\?))\2.)*?\1/g) || [];
|
||||
const quotesInStrings = stringMatches.join('').match(/["'`]/g)?.length || 0;
|
||||
const totalQuotes = (code.match(/["'`]/g) || []).length;
|
||||
if ((totalQuotes - quotesInStrings) % 2 !== 0) {
|
||||
warnings.push({
|
||||
type: 'inefficient',
|
||||
message: 'Possible unterminated string detected',
|
||||
suggestion: 'Check that all strings are properly closed'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic Python syntax validation
|
||||
*/
|
||||
private static validatePythonSyntax(
|
||||
code: string,
|
||||
errors: ValidationError[],
|
||||
warnings: ValidationWarning[]
|
||||
): void {
|
||||
// Check indentation consistency
|
||||
const lines = code.split('\n');
|
||||
const indentTypes = new Set<string>();
|
||||
|
||||
lines.forEach(line => {
|
||||
const indent = line.match(/^(\s+)/);
|
||||
if (indent) {
|
||||
if (indent[1].includes('\t')) indentTypes.add('tabs');
|
||||
if (indent[1].includes(' ')) indentTypes.add('spaces');
|
||||
}
|
||||
});
|
||||
|
||||
if (indentTypes.size > 1) {
|
||||
errors.push({
|
||||
type: 'invalid_value',
|
||||
property: 'pythonCode',
|
||||
message: 'Mixed tabs and spaces in indentation',
|
||||
fix: 'Use either tabs or spaces consistently, not both'
|
||||
});
|
||||
}
|
||||
|
||||
// Check for colons after control structures
|
||||
const controlStructures = /^\s*(if|elif|else|for|while|def|class|try|except|finally|with)\s+.*[^:]\s*$/gm;
|
||||
if (controlStructures.test(code)) {
|
||||
warnings.push({
|
||||
type: 'inefficient',
|
||||
message: 'Missing colon after control structure',
|
||||
suggestion: 'Add : at the end of if/for/def/class statements'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate n8n-specific code patterns
|
||||
*/
|
||||
private static validateN8nCodePatterns(
|
||||
code: string,
|
||||
language: string,
|
||||
warnings: ValidationWarning[]
|
||||
): void {
|
||||
// Check for return statement
|
||||
const hasReturn = language === 'python'
|
||||
? /return\s+/.test(code)
|
||||
: /return\s+/.test(code);
|
||||
|
||||
if (!hasReturn) {
|
||||
warnings.push({
|
||||
type: 'missing_common',
|
||||
message: 'No return statement found',
|
||||
suggestion: 'Code node should return data for the next node. Add: return items (Python) or return items; (JavaScript)'
|
||||
});
|
||||
}
|
||||
|
||||
// Check for common n8n patterns
|
||||
if (language === 'javascript') {
|
||||
if (!code.includes('items') && !code.includes('$input')) {
|
||||
warnings.push({
|
||||
type: 'missing_common',
|
||||
message: 'Code doesn\'t reference input items',
|
||||
suggestion: 'Access input data with: items or $input.all()'
|
||||
});
|
||||
}
|
||||
} else if (language === 'python') {
|
||||
if (!code.includes('items') && !code.includes('_input')) {
|
||||
warnings.push({
|
||||
type: 'missing_common',
|
||||
message: 'Code doesn\'t reference input items',
|
||||
suggestion: 'Access input data with: items variable'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,9 @@ export class EnhancedConfigValidator extends ConfigValidator {
|
||||
// Add operation-specific enhancements
|
||||
this.addOperationSpecificEnhancements(nodeType, config, enhancedResult);
|
||||
|
||||
// Deduplicate errors
|
||||
enhancedResult.errors = this.deduplicateErrors(enhancedResult.errors);
|
||||
|
||||
// Add examples from ExampleGenerator if there are errors
|
||||
if (enhancedResult.errors.length > 0) {
|
||||
this.addExamplesFromGenerator(nodeType, enhancedResult);
|
||||
@@ -202,6 +205,10 @@ export class EnhancedConfigValidator extends ConfigValidator {
|
||||
this.enhanceHttpRequestValidation(config, result);
|
||||
break;
|
||||
|
||||
case 'nodes-base.code':
|
||||
// Code node uses base validation which includes syntax checks
|
||||
break;
|
||||
|
||||
case 'nodes-base.openAi':
|
||||
NodeSpecificValidators.validateOpenAI(context);
|
||||
break;
|
||||
@@ -407,4 +414,31 @@ export class EnhancedConfigValidator extends ConfigValidator {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicate errors based on property and type
|
||||
* Prefers more specific error messages over generic ones
|
||||
*/
|
||||
private static deduplicateErrors(errors: ValidationError[]): ValidationError[] {
|
||||
const seen = new Map<string, ValidationError>();
|
||||
|
||||
for (const error of errors) {
|
||||
const key = `${error.property}-${error.type}`;
|
||||
const existing = seen.get(key);
|
||||
|
||||
if (!existing) {
|
||||
seen.set(key, error);
|
||||
} else {
|
||||
// Keep the error with more specific message or fix
|
||||
const existingLength = (existing.message?.length || 0) + (existing.fix?.length || 0);
|
||||
const newLength = (error.message?.length || 0) + (error.fix?.length || 0);
|
||||
|
||||
if (newLength > existingLength) {
|
||||
seen.set(key, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(seen.values());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user