Skip to content

New rule

Requirements

To add a new rule, the following items must be implemented:

  • A rule struct that implements the rule.Rule interface, including methods such as Validate and Lint.
  • The rule struct should be placed in a new or existing rule group. See directories under internal/linter/rule.
  • A default configuration entry must be added to internal/linter/dotgithub.yml.
  • The rule must be linked to its configuration key in the gen.go file.
  • Tests must be written.
  • Documentation must be updated.

See the sections below for more detailed guidance.

:warning: A single rule struct can serve multiple configuration keys. Please read the full documentation for details.

Rule group

Each rule belongs to a group. In the configuration file, these appear as second-level keys under the rules section — for example, naming_conventions or required_fields.

These groups map to directories under internal/rules. For example: * naming_conventionsinternal/rules/naming * required_fieldsinternal/rules/required

You can place your new rule in an existing group or create a new one.

Rule struct

The simplest way to start is by copying an existing rule and modifying it.

Every rule must implement the Rule interface from internal/linter/rule, shown below:

type Rule interface {
    Validate(conf interface{}) error
    Lint(
        config interface{},
        f dotgithub.File,
        d *dotgithub.DotGithub,
        chErrors chan<- glitch.Glitch,
    ) (bool, error)
    ConfigName(fileType int) string
    FileType() int
}
  • Validate: Checks if the configuration value is valid.
  • Lint: Runs the lint logic against a given file (workflow or action) using the provided configuration.
  • ConfigName: Returns the configuration key associated with the rule. The method receives an integer indicating the file type (action or workflow).
  • FileType: Returns an integer bitmask indicating which file types this rule applies to.

FileType method

If the rule applies only to action files:

func (r ActionReferencedStepOutputExists) FileType() int {
    return rule.DotGithubFileTypeAction
}

If the rule applies to both action and workflow files:

func (r ActionReferencedStepOutputExists) FileType() int {
    return rule.DotGithubFileTypeAction | rule.DotGithubFileTypeWorkflow
}

ConfigName method

This method can vary depending on whether the rule struct handles:

A single rule

func (r ActionReferencedStepOutputExists) ConfigName(int) string {
    return "dependencies__action_referenced_step_output_must_exist"
}

Different keys for different file types

func (r ReferencedInputExists) ConfigName(t int) string {
    switch t {
    case rule.DotGithubFileTypeWorkflow:
        return "dependencies__workflow_referenced_input_must_exists"
    case rule.DotGithubFileTypeAction:
        return "dependencies__action_referenced_input_must_exists"
    default:
        return "dependencies__*_referenced_input_must_exists"
    }
}

Keys based on a custom field

func (r Action) ConfigName(int) string {
    switch r.Field {
    case ActionFieldAction:
        return "required_fields__action_requires"
    case ActionFieldInput:
        return "required_fields__action_input_requires"
    case ActionFieldOutput:
        return "required_fields__action_output_requires"
    default:
        return "required_fields__action_*_requires"
    }
}

Lint method

To distinguish linting issues from internal errors, use glitch.Glitch instances and send them to the chErrors channel.

Use existing rules as a reference. Locate a similar rule in the configuration file (internal/linter/dotgithub.yml) and review its implementation.

Validate method

Use existing rules as templates depending on the type and complexity of the configuration value.

Configuration file

Your rule must be added to the default configuration file: internal/linter/dotgithub.yml. This defines default values and enables the rule by default.

When octo-linter parses the configuration file, it must map each configuration key to a rule struct. This is done using the registry generated in gen.go.

Refer back to the three ConfigName method patterns. Below are the corresponding gen.go entries:

Single Rule

            "dependencies__action_referenced_step_output_must_exist": {
                N: "dependencies.ActionReferencedStepOutputExists",
            },

Multiple Keys for File Types


            "dependencies__action_referenced_input_must_exists": {
                N: "dependencies.ReferencedInputExists",
            },
            // ...
            "dependencies__workflow_referenced_input_must_exists": {
                N: "dependencies.ReferencedInputExists",
            },

Rule Struct with Custom Field

            "required_fields__action_requires": {
                N: "required.Action",
                F: map[string]string{"Field": `required.ActionFieldAction`},
            },
            "required_fields__action_input_requires": {
                N: "required.Action",
                F: map[string]string{"Field": `required.ActionFieldInput`},
            },
            "required_fields__action_output_requires": {
                N: "required.Action",
                F: map[string]string{"Field": `required.ActionFieldOutput`},
            },

Documentation

Once your rule is implemented and tested, don’t forget to document it thoroughly. This ensures others understand its purpose and usage.