From ee289be24c90602685c460b90a5a697f40da3f7f Mon Sep 17 00:00:00 2001 From: sehwan505 Date: Tue, 25 Nov 2025 14:46:15 +0900 Subject: [PATCH 1/2] fix: update prompt for converter --- internal/converter/converter.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/converter/converter.go b/internal/converter/converter.go index 025cdce..15cccbf 100644 --- a/internal/converter/converter.go +++ b/internal/converter/converter.go @@ -47,10 +47,10 @@ func NewConverter(llmClient *llm.Client, outputDir string) *Converter { // ConvertResult represents the result of conversion type ConvertResult struct { - GeneratedFiles []string // List of generated file paths (including code-policy.json) + GeneratedFiles []string // List of generated file paths (including code-policy.json) CodePolicy *schema.CodePolicy // Generated code policy - Errors map[string]error // Errors per linter - Warnings []string // Conversion warnings + Errors map[string]error // Errors per linter + Warnings []string // Conversion warnings } // Convert is the main entry point for converting user policy to linter configs @@ -328,8 +328,10 @@ Available linters and NATIVE capabilities: - CANNOT: Complex business logic, context-aware rules, file naming, advanced async patterns - prettier: Code formatting ONLY (quotes, semicolons, indentation, line length, trailing commas) - tsc: TypeScript type checking ONLY (strict modes, noImplicitAny, strictNullChecks, type inference) -- checkstyle: Java style checks (naming, whitespace, imports, line length, complexity) -- pmd: Java code quality (unused code, empty blocks, naming conventions, design issues) +- checkstyle: Java naming and style checks ONLY (ClassName, MethodName, VariableName, ConstantName, ParameterName, LocalVariableName, LineLength, MethodLength, +ParameterNumber, FileLength, Indentation, WhitespaceAround, NeedBraces, LeftCurly, RightCurly, AvoidStarImport, +IllegalImport, UnusedImports, CyclomaticComplexity, NPathComplexity, JavadocMethod, JavadocType, MissingJavadocMethod) +- pmd: Java code quality (unused code, empty blocks, basic naming, design issues) STRICT Rules for selection: 1. ONLY select if the linter has a NATIVE rule that can enforce this From 67949bfb769790232ad157d06c8b765263eeaa07 Mon Sep 17 00:00:00 2001 From: sehwan505 Date: Tue, 25 Nov 2025 14:55:27 +0900 Subject: [PATCH 2/2] feat: enhance linter converters with validation and option registries --- internal/converter/linters/eslint.go | 60 ++- internal/converter/linters/prettier_tsc.go | 95 ++-- internal/converter/linters/registry.go | 488 +++++++++++++++++++++ 3 files changed, 592 insertions(+), 51 deletions(-) create mode 100644 internal/converter/linters/registry.go diff --git a/internal/converter/linters/eslint.go b/internal/converter/linters/eslint.go index 8584584..2acd931 100644 --- a/internal/converter/linters/eslint.go +++ b/internal/converter/linters/eslint.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "sort" "strings" "sync" @@ -126,7 +127,15 @@ func (c *ESLintLinterConverter) ConvertRules(ctx context.Context, rules []schema // convertSingleRule converts a single user rule to ESLint rule using LLM func (c *ESLintLinterConverter) convertSingleRule(ctx context.Context, rule schema.UserRule, llmClient *llm.Client) (string, interface{}, error) { - systemPrompt := `You are an ESLint configuration expert. Convert natural language coding rules to ESLint rule configurations. + // Build list of valid ESLint rules for the prompt + validRules := GetESLintRuleNames() + sort.Strings(validRules) + validRulesStr := strings.Join(validRules, ", ") + + systemPrompt := fmt.Sprintf(`You are an ESLint configuration expert. Convert natural language coding rules to ESLint rule configurations. + +IMPORTANT: You MUST ONLY use rules from this exact list of valid ESLint rules: +%s Return ONLY a JSON object (no markdown fences) with this structure: { @@ -135,22 +144,11 @@ Return ONLY a JSON object (no markdown fences) with this structure: "options": {...} } -Common ESLint rules: -- Naming: camelcase, id-match, id-length, new-cap -- Console: no-console, no-debugger, no-alert -- Code Quality: no-unused-vars, no-undef, eqeqeq, prefer-const, no-var -- Complexity: complexity, max-depth, max-nested-callbacks, max-lines-per-function -- Length: max-len, max-lines, max-params, max-statements -- Style: indent, quotes, semi, comma-dangle, brace-style -- Imports: no-restricted-imports -- Security: no-eval, no-implied-eval, no-new-func - -If the rule cannot be expressed in ESLint, return: -{ - "rule_name": "", - "severity": "off", - "options": null -} +CRITICAL RULES: +1. ONLY use rule names from the list above - do NOT invent or guess rule names +2. If no rule from the list can enforce this requirement, return rule_name as empty string "" +3. Do NOT suggest plugin rules (e.g., @typescript-eslint/*, eslint-plugin-*) +4. When in doubt, return empty rule_name - it's better to skip than use wrong rule Examples: @@ -176,7 +174,25 @@ Output: "rule_name": "camelcase", "severity": "error", "options": {"properties": "always"} -}` +} + +Input: "File names must be kebab-case" +Output: +{ + "rule_name": "", + "severity": "off", + "options": null +} +(Reason: No native ESLint rule for file naming) + +Input: "No hardcoded API keys" +Output: +{ + "rule_name": "", + "severity": "off", + "options": null +} +(Reason: Requires plugin or semantic analysis)`, validRulesStr) userPrompt := fmt.Sprintf("Convert this rule to ESLint configuration:\n\n%s", rule.Say) if rule.Severity != "" { @@ -211,6 +227,14 @@ Output: return "", nil, nil } + // VALIDATION: Check if the rule actually exists in our registry + validation := ValidateESLintRule(result.RuleName, result.Options) + if !validation.Valid { + // Rule doesn't exist - skip it (will be handled by llm-validator) + fmt.Printf("⚠️ Invalid ESLint rule '%s': %s\n", result.RuleName, validation.Message) + return "", nil, nil + } + // Map user severity to ESLint severity if needed severity := mapSeverity(rule.Severity) if severity == "" { diff --git a/internal/converter/linters/prettier_tsc.go b/internal/converter/linters/prettier_tsc.go index 2b0ba51..5426e88 100644 --- a/internal/converter/linters/prettier_tsc.go +++ b/internal/converter/linters/prettier_tsc.go @@ -72,22 +72,22 @@ func (c *PrettierLinterConverter) ConvertRules(ctx context.Context, rules []sche // convertSingleRule converts a single user rule to Prettier config using LLM func (c *PrettierLinterConverter) convertSingleRule(ctx context.Context, rule schema.UserRule, llmClient *llm.Client) (map[string]interface{}, error) { - systemPrompt := `You are a Prettier configuration expert. Convert natural language formatting rules to Prettier configuration options. + // Build list of valid Prettier options for the prompt + validOptions := GetPrettierOptionNames() + validOptionsStr := strings.Join(validOptions, ", ") -Return ONLY a JSON object (no markdown fences) with Prettier options. + systemPrompt := fmt.Sprintf(`You are a Prettier configuration expert. Convert natural language formatting rules to Prettier configuration options. + +IMPORTANT: You MUST ONLY use options from this exact list of valid Prettier options: +%s -Available Prettier options: -- semi: true/false (use semicolons) -- singleQuote: true/false (use single quotes) -- tabWidth: number (spaces per indentation level) -- useTabs: true/false (use tabs instead of spaces) -- trailingComma: "none"/"es5"/"all" (trailing commas) -- printWidth: number (line length) -- arrowParens: "always"/"avoid" (arrow function parentheses) -- bracketSpacing: true/false (spaces in object literals) -- endOfLine: "lf"/"crlf"/"auto" +Return ONLY a JSON object (no markdown fences) with Prettier options. +If the rule cannot be expressed with Prettier options, return empty object: {} -If the rule is not about formatting, return empty object: {} +CRITICAL RULES: +1. ONLY use option names from the list above +2. Do NOT invent new options +3. If no option can enforce this rule, return {} Examples: @@ -114,7 +114,12 @@ Input: "Maximum line length is 120 characters" Output: { "printWidth": 120 -}` +} + +Input: "Sort imports alphabetically" +Output: +{} +(Reason: No native Prettier option for this)`, validOptionsStr) userPrompt := fmt.Sprintf("Convert this rule to Prettier configuration:\n\n%s", rule.Say) @@ -136,7 +141,18 @@ Output: return nil, fmt.Errorf("failed to parse LLM response: %w", err) } - return config, nil + // VALIDATION: Filter out invalid options + validConfig := make(map[string]interface{}) + for key, value := range config { + validation := ValidatePrettierOption(key, value) + if validation.Valid { + validConfig[key] = value + } else { + fmt.Printf("⚠️ Invalid Prettier option '%s': %s\n", key, validation.Message) + } + } + + return validConfig, nil } // TSCLinterConverter converts rules to TypeScript compiler configuration @@ -212,25 +228,22 @@ func (c *TSCLinterConverter) ConvertRules(ctx context.Context, rules []schema.Us // convertSingleRule converts a single user rule to TypeScript compiler option using LLM func (c *TSCLinterConverter) convertSingleRule(ctx context.Context, rule schema.UserRule, llmClient *llm.Client) (map[string]interface{}, error) { - systemPrompt := `You are a TypeScript compiler configuration expert. Convert natural language type-checking rules to tsconfig.json compiler options. + // Build list of valid TSC options for the prompt + validOptions := GetTSCOptionNames() + validOptionsStr := strings.Join(validOptions, ", ") + + systemPrompt := fmt.Sprintf(`You are a TypeScript compiler configuration expert. Convert natural language type-checking rules to tsconfig.json compiler options. + +IMPORTANT: You MUST ONLY use options from this exact list of valid TypeScript compiler options: +%s Return ONLY a JSON object (no markdown fences) with TypeScript compiler options. +If the rule cannot be expressed with TypeScript compiler options, return empty object: {} -Available TypeScript compiler options: -- strict: true/false (enable all strict checks) -- noImplicitAny: true/false (error on implicit any) -- strictNullChecks: true/false (strict null checking) -- strictFunctionTypes: true/false (strict function types) -- strictBindCallApply: true/false (strict bind/call/apply) -- noUnusedLocals: true/false (error on unused locals) -- noUnusedParameters: true/false (error on unused parameters) -- noImplicitReturns: true/false (error on implicit returns) -- noFallthroughCasesInSwitch: true/false (error on fallthrough) -- noUncheckedIndexedAccess: true/false (undefined in index signatures) -- allowUnreachableCode: true/false (allow unreachable code) -- allowUnusedLabels: true/false (allow unused labels) - -If the rule is not about TypeScript type-checking, return empty object: {} +CRITICAL RULES: +1. ONLY use option names from the list above +2. Do NOT invent new options +3. If no option can enforce this rule, return {} Examples: @@ -257,7 +270,12 @@ Input: "Enable all strict type checks" Output: { "strict": true -}` +} + +Input: "Functions must have return type annotations" +Output: +{} +(Reason: No native TSC option for this - requires plugin)`, validOptionsStr) userPrompt := fmt.Sprintf("Convert this rule to TypeScript compiler configuration:\n\n%s", rule.Say) @@ -279,5 +297,16 @@ Output: return nil, fmt.Errorf("failed to parse LLM response: %w", err) } - return config, nil + // VALIDATION: Filter out invalid options + validConfig := make(map[string]interface{}) + for key, value := range config { + validation := ValidateTSCOption(key, value) + if validation.Valid { + validConfig[key] = value + } else { + fmt.Printf("⚠️ Invalid TSC option '%s': %s\n", key, validation.Message) + } + } + + return validConfig, nil } diff --git a/internal/converter/linters/registry.go b/internal/converter/linters/registry.go new file mode 100644 index 0000000..b0fff0a --- /dev/null +++ b/internal/converter/linters/registry.go @@ -0,0 +1,488 @@ +package linters + +// ESLintRuleRegistry contains all valid native ESLint rules with their options schema +// This is used to validate LLM-generated rules and prevent invalid configurations +var ESLintRuleRegistry = map[string]RuleDefinition{ + // Console/Debug + "no-console": { + Description: "Disallow the use of console", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "allow": {Type: "array", Items: "string"}, + }, + }, + }, + "no-debugger": { + Description: "Disallow the use of debugger", + }, + "no-alert": { + Description: "Disallow the use of alert, confirm, and prompt", + }, + + // Variables + "no-unused-vars": { + Description: "Disallow unused variables", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "vars": {Type: "string", Enum: []string{"all", "local"}}, + "varsIgnorePattern": {Type: "string"}, + "args": {Type: "string", Enum: []string{"all", "after-used", "none"}}, + "argsIgnorePattern": {Type: "string"}, + "caughtErrors": {Type: "string", Enum: []string{"all", "none"}}, + "ignoreRestSiblings": {Type: "boolean"}, + }, + }, + }, + "no-undef": { + Description: "Disallow the use of undeclared variables", + }, + "no-var": { + Description: "Require let or const instead of var", + }, + "prefer-const": { + Description: "Require const declarations for variables that are never reassigned", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "destructuring": {Type: "string", Enum: []string{"any", "all"}}, + "ignoreReadBeforeAssign": {Type: "boolean"}, + }, + }, + }, + + // Naming + "camelcase": { + Description: "Enforce camelcase naming convention", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "properties": {Type: "string", Enum: []string{"always", "never"}}, + "ignoreDestructuring": {Type: "boolean"}, + "ignoreImports": {Type: "boolean"}, + "ignoreGlobals": {Type: "boolean"}, + "allow": {Type: "array", Items: "string"}, + }, + }, + }, + "new-cap": { + Description: "Require constructor names to begin with a capital letter", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "newIsCap": {Type: "boolean"}, + "capIsNew": {Type: "boolean"}, + "newIsCapExceptions": {Type: "array", Items: "string"}, + "capIsNewExceptions": {Type: "array", Items: "string"}, + "properties": {Type: "boolean"}, + }, + }, + }, + "id-length": { + Description: "Enforce minimum and maximum identifier lengths", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "min": {Type: "number"}, + "max": {Type: "number"}, + "properties": {Type: "string", Enum: []string{"always", "never"}}, + "exceptions": {Type: "array", Items: "string"}, + }, + }, + }, + "id-match": { + Description: "Require identifiers to match a specified regular expression", + Options: OptionsSchema{ + Type: "string", // regex pattern + }, + }, + + // Code Quality + "eqeqeq": { + Description: "Require the use of === and !==", + Options: OptionsSchema{ + Type: "string", + Enum: []string{"always", "smart"}, + }, + }, + "no-eval": { + Description: "Disallow the use of eval()", + }, + "no-implied-eval": { + Description: "Disallow the use of eval()-like methods", + }, + "no-new-func": { + Description: "Disallow new operators with the Function object", + }, + + // Complexity + "complexity": { + Description: "Enforce a maximum cyclomatic complexity", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "max": {Type: "number"}, + }, + }, + }, + "max-depth": { + Description: "Enforce a maximum depth that blocks can be nested", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "max": {Type: "number"}, + }, + }, + }, + "max-nested-callbacks": { + Description: "Enforce a maximum depth that callbacks can be nested", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "max": {Type: "number"}, + }, + }, + }, + + // Length/Size + "max-len": { + Description: "Enforce a maximum line length", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "code": {Type: "number"}, + "tabWidth": {Type: "number"}, + "comments": {Type: "number"}, + "ignorePattern": {Type: "string"}, + "ignoreComments": {Type: "boolean"}, + "ignoreTrailingComments": {Type: "boolean"}, + "ignoreUrls": {Type: "boolean"}, + "ignoreStrings": {Type: "boolean"}, + "ignoreTemplateLiterals": {Type: "boolean"}, + "ignoreRegExpLiterals": {Type: "boolean"}, + }, + }, + }, + "max-lines": { + Description: "Enforce a maximum number of lines per file", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "max": {Type: "number"}, + "skipBlankLines": {Type: "boolean"}, + "skipComments": {Type: "boolean"}, + }, + }, + }, + "max-lines-per-function": { + Description: "Enforce a maximum number of lines of code in a function", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "max": {Type: "number"}, + "skipBlankLines": {Type: "boolean"}, + "skipComments": {Type: "boolean"}, + "IIFEs": {Type: "boolean"}, + }, + }, + }, + "max-params": { + Description: "Enforce a maximum number of parameters in function definitions", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "max": {Type: "number"}, + }, + }, + }, + "max-statements": { + Description: "Enforce a maximum number of statements allowed in function blocks", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "max": {Type: "number"}, + "ignoreTopLevelFunctions": {Type: "boolean"}, + }, + }, + }, + + // Style + "indent": { + Description: "Enforce consistent indentation", + Options: OptionsSchema{ + Type: "mixed", // number or "tab" + }, + }, + "quotes": { + Description: "Enforce the consistent use of either backticks, double, or single quotes", + Options: OptionsSchema{ + Type: "string", + Enum: []string{"single", "double", "backtick"}, + }, + }, + "semi": { + Description: "Require or disallow semicolons instead of ASI", + Options: OptionsSchema{ + Type: "string", + Enum: []string{"always", "never"}, + }, + }, + "comma-dangle": { + Description: "Require or disallow trailing commas", + Options: OptionsSchema{ + Type: "string", + Enum: []string{"never", "always", "always-multiline", "only-multiline"}, + }, + }, + "brace-style": { + Description: "Enforce consistent brace style for blocks", + Options: OptionsSchema{ + Type: "string", + Enum: []string{"1tbs", "stroustrup", "allman"}, + }, + }, + + // Imports + "no-restricted-imports": { + Description: "Disallow specified modules when loaded by import", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "paths": {Type: "array", Items: "string"}, + "patterns": {Type: "array", Items: "string"}, + }, + }, + }, + "no-duplicate-imports": { + Description: "Disallow duplicate module imports", + }, + + // Best Practices + "curly": { + Description: "Enforce consistent brace style for all control statements", + Options: OptionsSchema{ + Type: "string", + Enum: []string{"all", "multi", "multi-line", "multi-or-nest", "consistent"}, + }, + }, + "dot-notation": { + Description: "Enforce dot notation whenever possible", + }, + "no-else-return": { + Description: "Disallow else blocks after return statements in if statements", + }, + "no-empty": { + Description: "Disallow empty block statements", + }, + "no-empty-function": { + Description: "Disallow empty functions", + }, + "no-magic-numbers": { + Description: "Disallow magic numbers", + Options: OptionsSchema{ + Type: "object", + Properties: map[string]OptionProperty{ + "ignore": {Type: "array", Items: "number"}, + "ignoreArrayIndexes": {Type: "boolean"}, + "ignoreDefaultValues": {Type: "boolean"}, + "enforceConst": {Type: "boolean"}, + "detectObjects": {Type: "boolean"}, + }, + }, + }, + "no-throw-literal": { + Description: "Disallow throwing literals as exceptions", + }, + "no-useless-return": { + Description: "Disallow redundant return statements", + }, + "require-await": { + Description: "Disallow async functions which have no await expression", + }, +} + +// PrettierOptionRegistry contains all valid Prettier options +var PrettierOptionRegistry = map[string]OptionProperty{ + "printWidth": {Type: "number", Default: 80}, + "tabWidth": {Type: "number", Default: 2}, + "useTabs": {Type: "boolean", Default: false}, + "semi": {Type: "boolean", Default: true}, + "singleQuote": {Type: "boolean", Default: false}, + "quoteProps": {Type: "string", Enum: []string{"as-needed", "consistent", "preserve"}}, + "jsxSingleQuote": {Type: "boolean", Default: false}, + "trailingComma": {Type: "string", Enum: []string{"all", "es5", "none"}}, + "bracketSpacing": {Type: "boolean", Default: true}, + "bracketSameLine": {Type: "boolean", Default: false}, + "arrowParens": {Type: "string", Enum: []string{"always", "avoid"}}, + "proseWrap": {Type: "string", Enum: []string{"always", "never", "preserve"}}, + "htmlWhitespaceSensitivity": {Type: "string", Enum: []string{"css", "strict", "ignore"}}, + "endOfLine": {Type: "string", Enum: []string{"lf", "crlf", "cr", "auto"}}, + "singleAttributePerLine": {Type: "boolean", Default: false}, +} + +// TSCOptionRegistry contains all valid TypeScript compiler options for linting +var TSCOptionRegistry = map[string]OptionProperty{ + // Strict Checks + "strict": {Type: "boolean", Default: false}, + "noImplicitAny": {Type: "boolean", Default: false}, + "strictNullChecks": {Type: "boolean", Default: false}, + "strictFunctionTypes": {Type: "boolean", Default: false}, + "strictBindCallApply": {Type: "boolean", Default: false}, + "strictPropertyInitialization": {Type: "boolean", Default: false}, + "noImplicitThis": {Type: "boolean", Default: false}, + "useUnknownInCatchVariables": {Type: "boolean", Default: false}, + "alwaysStrict": {Type: "boolean", Default: false}, + + // Linting + "noUnusedLocals": {Type: "boolean", Default: false}, + "noUnusedParameters": {Type: "boolean", Default: false}, + "exactOptionalPropertyTypes": {Type: "boolean", Default: false}, + "noImplicitReturns": {Type: "boolean", Default: false}, + "noFallthroughCasesInSwitch": {Type: "boolean", Default: false}, + "noUncheckedIndexedAccess": {Type: "boolean", Default: false}, + "noImplicitOverride": {Type: "boolean", Default: false}, + "noPropertyAccessFromIndexSignature": {Type: "boolean", Default: false}, + "allowUnusedLabels": {Type: "boolean", Default: true}, + "allowUnreachableCode": {Type: "boolean", Default: true}, +} + +// RuleDefinition defines a linter rule's schema +type RuleDefinition struct { + Description string + Options OptionsSchema + Deprecated bool + Replacement string // If deprecated, which rule replaces it +} + +// OptionsSchema defines the schema for rule options +type OptionsSchema struct { + Type string // "object", "string", "number", "boolean", "array", "mixed" + Properties map[string]OptionProperty // For object type + Items string // For array type, element type + Enum []string // Valid values for string type +} + +// OptionProperty defines a single option property +type OptionProperty struct { + Type string + Enum []string + Items string // For arrays + Default interface{} // Default value +} + +// ValidateESLintRule checks if a rule name and options are valid +func ValidateESLintRule(ruleName string, options interface{}) ValidationError { + def, exists := ESLintRuleRegistry[ruleName] + if !exists { + return ValidationError{ + Valid: false, + Message: "unknown ESLint rule: " + ruleName, + Suggestion: "This rule may require a plugin or doesn't exist. " + + "Consider using llm-validator for this check instead.", + } + } + + if def.Deprecated { + return ValidationError{ + Valid: false, + Message: "deprecated rule: " + ruleName, + Suggestion: "Use '" + def.Replacement + "' instead.", + } + } + + // TODO: Add options validation based on OptionsSchema + return ValidationError{Valid: true} +} + +// ValidatePrettierOption checks if a Prettier option is valid +func ValidatePrettierOption(optionName string, value interface{}) ValidationError { + def, exists := PrettierOptionRegistry[optionName] + if !exists { + return ValidationError{ + Valid: false, + Message: "unknown Prettier option: " + optionName, + } + } + + // Validate type and enum if applicable + if len(def.Enum) > 0 { + strVal, ok := value.(string) + if ok { + valid := false + for _, allowed := range def.Enum { + if strVal == allowed { + valid = true + break + } + } + if !valid { + return ValidationError{ + Valid: false, + Message: "invalid value for " + optionName, + Suggestion: "Valid values: " + joinStrings(def.Enum), + } + } + } + } + + return ValidationError{Valid: true} +} + +// ValidateTSCOption checks if a TypeScript compiler option is valid +func ValidateTSCOption(optionName string, value interface{}) ValidationError { + _, exists := TSCOptionRegistry[optionName] + if !exists { + return ValidationError{ + Valid: false, + Message: "unknown TypeScript compiler option: " + optionName, + } + } + + return ValidationError{Valid: true} +} + +// ValidationError represents a validation result +type ValidationError struct { + Valid bool + Message string + Suggestion string +} + +func joinStrings(strs []string) string { + result := "" + for i, s := range strs { + if i > 0 { + result += ", " + } + result += s + } + return result +} + +// GetESLintRuleNames returns all valid ESLint rule names +func GetESLintRuleNames() []string { + names := make([]string, 0, len(ESLintRuleRegistry)) + for name := range ESLintRuleRegistry { + names = append(names, name) + } + return names +} + +// GetPrettierOptionNames returns all valid Prettier option names +func GetPrettierOptionNames() []string { + names := make([]string, 0, len(PrettierOptionRegistry)) + for name := range PrettierOptionRegistry { + names = append(names, name) + } + return names +} + +// GetTSCOptionNames returns all valid TypeScript compiler option names +func GetTSCOptionNames() []string { + names := make([]string, 0, len(TSCOptionRegistry)) + for name := range TSCOptionRegistry { + names = append(names, name) + } + return names +}