Skip to content

feat: upgrade converter with validation#30

Closed
sehwan505 wants to merge 2 commits intomainfrom
feature/upgrade-linter
Closed

feat: upgrade converter with validation#30
sehwan505 wants to merge 2 commits intomainfrom
feature/upgrade-linter

Conversation

@sehwan505
Copy link
Contributor

upgrade converter with validation

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the converter with validation capabilities to ensure LLM-generated linter configurations use only valid, native rules and options. It introduces registries for ESLint rules, Prettier options, and TypeScript compiler options, validates LLM outputs against these registries, and improves prompts to guide the LLM toward valid configurations.

Key Changes

  • New validation registry: Added registry.go with comprehensive registries of valid ESLint rules (60+ rules), Prettier options (14 options), and TypeScript compiler options (20+ options) along with their schemas
  • Validation integration: Integrated validation into the ESLint, Prettier, and TSC converters to filter out invalid LLM-generated options
  • Enhanced LLM prompts: Updated system prompts to explicitly list valid options and provide clearer guidance on when to return empty configurations

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 12 comments.

File Description
internal/converter/linters/registry.go New file defining validation registries and functions for ESLint rules, Prettier options, and TSC options with comprehensive rule definitions and validation logic
internal/converter/linters/prettier_tsc.go Integrated validation for Prettier and TSC options, updated prompts to include valid option lists, and added filtering logic to remove invalid options
internal/converter/linters/eslint.go Added ESLint rule validation, enhanced prompts with complete rule list, and integrated validation to reject invalid rules
internal/converter/converter.go Updated documentation to expand checkstyle capabilities description with more specific rule names

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

Comment on lines +234 to +235
fmt.Printf("⚠️ Invalid ESLint rule '%s': %s\n", result.RuleName, validation.Message)
return "", nil, nil
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using fmt.Printf to output warnings directly to stdout is not ideal for library code. Consider one of these alternatives:

  1. Use a logger interface that can be injected (e.g., log.Printf or a structured logger)
  2. Collect warnings in the result structure and return them
  3. Return warnings through the error return value using a custom error type that includes warnings

This would make the code more testable and allow callers to control how warnings are handled.

Copilot uses AI. Check for mistakes.
}
}

// TODO: Add options validation based on OptionsSchema
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TODO comment indicates that options validation is not yet implemented. This means the validation only checks if a rule name exists, but not whether the provided options match the rule's expected schema. Consider implementing this validation to ensure the LLM-generated options conform to the rule's schema definition, or create a tracking issue if this is intended for future work.

Suggested change
// TODO: Add options validation based on OptionsSchema
// Validate options against OptionsSchema
schema := def.Options
// If no options schema, accept any options
if schema.Type == "" {
return ValidationError{Valid: true}
}
// If schema expects an object, options should be a map[string]interface{}
if schema.Type == "object" {
optsMap, ok := options.(map[string]interface{})
if !ok {
return ValidationError{
Valid: false,
Message: "options should be an object (map)",
Suggestion: "Provide options as a key-value map.",
}
}
for propName, propSchema := range schema.Properties {
val, exists := optsMap[propName]
if !exists {
continue // Option is optional if not present
}
// Type check
switch propSchema.Type {
case "string":
_, ok := val.(string)
if !ok {
return ValidationError{
Valid: false,
Message: "option '" + propName + "' should be a string",
Suggestion: "Provide a string value for '" + propName + "'.",
}
}
// Enum check
if len(propSchema.Enum) > 0 {
strVal := val.(string)
valid := false
for _, allowed := range propSchema.Enum {
if strVal == allowed {
valid = true
break
}
}
if !valid {
return ValidationError{
Valid: false,
Message: "invalid value for '" + propName + "'",
Suggestion: "Valid values: " + joinStrings(propSchema.Enum),
}
}
}
case "array":
arr, ok := val.([]interface{})
if !ok {
return ValidationError{
Valid: false,
Message: "option '" + propName + "' should be an array",
Suggestion: "Provide an array value for '" + propName + "'.",
}
}
// Check item type
for _, item := range arr {
switch propSchema.Items {
case "string":
if _, ok := item.(string); !ok {
return ValidationError{
Valid: false,
Message: "array item in '" + propName + "' should be a string",
Suggestion: "All items in '" + propName + "' should be strings.",
}
}
// Add other types as needed
}
}
case "number":
_, ok := val.(float64)
if !ok {
return ValidationError{
Valid: false,
Message: "option '" + propName + "' should be a number",
Suggestion: "Provide a numeric value for '" + propName + "'.",
}
}
case "boolean":
_, ok := val.(bool)
if !ok {
return ValidationError{
Valid: false,
Message: "option '" + propName + "' should be a boolean",
Suggestion: "Provide a boolean value for '" + propName + "'.",
}
}
// Add other types as needed
}
}
}
// Add support for other schema types if needed

Copilot uses AI. Check for mistakes.
Comment on lines +331 to +333
- 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)
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] These lines are very long (over 160 characters) which may affect readability and could exceed style guide limits. Consider breaking the checkstyle capabilities list into multiple lines for better readability:

- 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)
Suggested change
- 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)
- 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
)

Copilot uses AI. Check for mistakes.
Comment on lines +144 to +155
// 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
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new validation logic that filters out invalid Prettier options lacks test coverage. Consider adding tests to verify:

  1. Valid options are kept in the result
  2. Invalid options are filtered out
  3. The warning message is logged (or consider returning warnings in a testable way)
  4. Empty configuration is handled correctly when all options are invalid

Copilot uses AI. Check for mistakes.
Comment on lines +300 to +311
// 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
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new validation logic that filters out invalid TSC options lacks test coverage. Consider adding tests to verify:

  1. Valid options are kept in the result
  2. Invalid options are filtered out
  3. The warning message is logged (or consider returning warnings in a testable way)
  4. Empty configuration is handled correctly when all options are invalid

Copilot uses AI. Check for mistakes.
Comment on lines +307 to +308
fmt.Printf("⚠️ Invalid TSC option '%s': %s\n", key, validation.Message)
}
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using fmt.Printf to output warnings directly to stdout is not ideal for library code. Consider one of these alternatives:

  1. Use a logger interface that can be injected (e.g., log.Printf or a structured logger)
  2. Collect warnings in the result structure and return them
  3. Return warnings through the error return value using a custom error type that includes warnings

This would make the code more testable and allow callers to control how warnings are handled.

Copilot uses AI. Check for mistakes.
Comment on lines +408 to +427
// 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),
}
}
}
}
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation only checks enum values when the value is already a string (if ok), but doesn't validate when:

  1. The value type doesn't match the expected type (e.g., a number when a string is expected)
  2. The value is not a string but enum validation is required

Consider adding type validation before enum validation. For example:

// Validate type
switch def.Type {
case "string":
    if _, ok := value.(string); !ok {
        return ValidationError{Valid: false, Message: "expected string value for " + optionName}
    }
case "number":
    if _, ok := value.(float64); !ok {
        return ValidationError{Valid: false, Message: "expected number value for " + optionName}
    }
case "boolean":
    if _, ok := value.(bool); !ok {
        return ValidationError{Valid: false, Message: "expected boolean value for " + optionName}
    }
}

Copilot uses AI. Check for mistakes.
Message: "unknown TypeScript compiler option: " + optionName,
}
}

Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ValidateTSCOption function only checks if the option exists but doesn't validate:

  1. The value type (all TSC options in the registry are booleans with defaults, but no type checking is performed)
  2. The value is appropriate for the option

Consider adding type validation similar to ValidatePrettierOption to ensure boolean values are actually booleans. For example:

def, exists := TSCOptionRegistry[optionName]
if !exists {
    return ValidationError{Valid: false, Message: "unknown TypeScript compiler option: " + optionName}
}

// Validate type (all TSC options are boolean)
if _, ok := value.(bool); !ok {
    return ValidationError{Valid: false, Message: "expected boolean value for " + optionName}
}
Suggested change
// Validate type (all TSC options are boolean)
if _, ok := value.(bool); !ok {
return ValidationError{
Valid: false,
Message: "expected boolean value for " + optionName,
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +374 to +488
// 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
}
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new validation functions (ValidateESLintRule, ValidatePrettierOption, ValidateTSCOption) and registry helper functions (GetESLintRuleNames, GetPrettierOptionNames, GetTSCOptionNames) lack test coverage. Given that the repository has comprehensive tests in the tests/ directory, consider adding unit tests to verify:

  1. Valid rule/option names are accepted
  2. Invalid rule/option names are rejected with appropriate error messages
  3. Deprecated rules are detected (for ESLint)
  4. Enum validation works correctly (for Prettier)
  5. Type validation works as expected
  6. Edge cases (empty strings, nil values, etc.)

Copilot uses AI. Check for mistakes.
Comment on lines +151 to +152
fmt.Printf("⚠️ Invalid Prettier option '%s': %s\n", key, validation.Message)
}
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using fmt.Printf to output warnings directly to stdout is not ideal for library code. Consider one of these alternatives:

  1. Use a logger interface that can be injected (e.g., log.Printf or a structured logger)
  2. Collect warnings in the result structure and return them
  3. Return warnings through the error return value using a custom error type that includes warnings

This would make the code more testable and allow callers to control how warnings are handled.

Copilot uses AI. Check for mistakes.
@sehwan505 sehwan505 closed this Dec 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants