linter

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package linter implements the decolint engine: it parses configuration files as HuJSON (JSONC), runs lint rules against the syntax tree, and filters findings suppressed by ignore comments. Locating the configuration files a devcontainer directory contains is the discovery package's responsibility.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RunArgs added in v0.6.0

func RunArgs(arr *hujson.Array) []dockerargs.Arg

RunArgs returns every "docker run" flag occurrence in arr, a devcontainer.json's "runArgs", as dockerargs.Parse reads the argv the array becomes; dockerargs.Arg.Index indexes arr.Elements. An element that is not a string, which the devcontainer tooling could not hand to docker at all, stands in as an empty entry so that the elements around it keep the positions docker would read them at.

It is the reading the traversal hands "/runArgs/--flag" patterns (see Rule.Paths), for the rules that cannot be driven by it because they report a flag's absence.

Types

type Category added in v0.1.0

type Category int

Category classifies a rule by the kind of problem it reports. Every rule belongs to exactly one category, so users can adjust the severity of a whole class of rules at once.

const (

	// CategoryCorrectness marks a rule that reports configuration that is invalid or does not
	// behave as written.
	CategoryCorrectness Category = iota + 1
	// CategorySecurity marks a rule that reports container runtime privilege and hardening issues.
	CategorySecurity
	// CategoryReproducibility marks a rule that reports unpinned versions or digests that make the
	// resulting environment change over time.
	CategoryReproducibility
	// CategoryStyle marks a rule that reports discouraged or legacy configuration that still works.
	CategoryStyle
)

func ParseCategory added in v0.1.0

func ParseCategory(name string) (Category, error)

ParseCategory parses a category name, matched case-insensitively, into a Category. It returns an error if name does not name a known category.

func (Category) String added in v0.1.0

func (c Category) String() string

String returns the category's name, as used in configuration files and output.

type Context

type Context struct {
	// Path is the path of the file being linted, for reporting only: it is spelled however the
	// caller named the file, so a rule must not read anything into its shape. Use [Dir] for the
	// containing directory.
	Path string
	// Type is the kind of configuration file being linted.
	Type FileType
	// Root is the HuJSON syntax tree of the file. It preserves comments and byte offsets into the
	// original source.
	Root *hujson.Value
	// Dir describes the directory containing the file being linted.
	Dir Dir
}

Context carries everything a rule needs to inspect a single configuration file.

type Dir added in v0.4.0

type Dir struct {
	// FS gives read access to the directory's contents, confined to the lint root. It is nil when
	// the caller has no backing filesystem, e.g. an in-memory document.
	FS fs.FS
	// Name is the directory's own name, taken from where the directory actually is rather than from
	// [Context.Path]. It is empty when the caller has no directory to name.
	Name string
}

Dir is the directory a configuration file is linted in. Each field is independently optional, and a rule that needs one must return no findings when it is unset.

type Document added in v0.2.0

type Document struct {
	// contains filtered or unexported fields
}

Document is a parsed configuration file together with the indexes rule application needs: the syntax tree, a byte-offset-to-line resolver, and the ignore directives found in it. Produce one with ParseDocument, then apply rules with Linter.LintDocument. The position and ignore indexes are internal; the syntax tree is exposed via Tree.

func ParseDocument added in v0.2.0

func ParseDocument(src []byte) (*Document, error)

ParseDocument parses src as HuJSON and precomputes the line index and ignore directives used when applying rules. Ignore directives are read from the source as authored, before any mutation of the tree returned by Tree.

func (*Document) Tree added in v0.2.0

func (d *Document) Tree() *hujson.Value

Tree returns the file's HuJSON syntax tree. It may be mutated before LintDocument runs, e.g. to merge Feature-contributed properties into the effective configuration, but any node added must carry offsets pointing into the original source, since findings are still positioned against that source. Ignore directives are read at parse time and unaffected by later mutation.

type Example added in v0.5.0

type Example struct {
	// Bad is configuration the rule reports.
	Bad Snippet
	// Good is configuration the rule does not report, typically Bad with the problem fixed.
	Good Snippet
	// Note is Markdown prose shown after Good, for context Bad and Good alone don't convey (e.g. why
	// Good is scoped the way it is). It is optional.
	Note string
}

Example pairs configuration that trips a rule with configuration that doesn't, for the rule's documentation.

type ExampleFile added in v0.5.0

type ExampleFile struct {
	// Path is the file's path, relative to the directory the example is linted in (e.g.
	// "devcontainer.json" or ".devcontainer/devcontainer.json").
	Path string
	// Content is the file's content.
	Content string
	// Mode is the file's permission bits. The zero value means a regular file at the default mode;
	// set it for a rule that inspects permissions (e.g. whether install.sh is executable).
	Mode fs.FileMode
}

ExampleFile is one file of a Snippet.

type FileType

type FileType string

FileType identifies the kind of configuration file being linted.

const (
	// Devcontainer is a devcontainer.json file.
	Devcontainer FileType = "devcontainer"
	// Feature is a devcontainer-feature.json file.
	Feature FileType = "feature"
	// Template is a devcontainer-template.json file.
	Template FileType = "template"
)

type Finding

type Finding struct {
	// Message describes the problem in a human-readable way.
	Message string
	// Offset is the byte offset into the original source where the problem is located, typically the
	// StartOffset of the offending value.
	Offset int
}

Finding is a single problem reported by a rule.

type Issue

type Issue struct {
	Path     string   `json:"path"`
	Line     int      `json:"line"` // 1-based
	Col      int      `json:"col"`  // 1-based, in bytes
	RuleID   string   `json:"ruleId"`
	Message  string   `json:"message"`
	Severity Severity `json:"severity"`
}

Issue is a rule finding resolved to a file position.

func (Issue) String

func (i Issue) String() string

type Linter

type Linter struct {
	// contains filtered or unexported fields
}

Linter runs a set of rules against devcontainer configuration files.

func New

func New() *Linter

New returns an empty Linter. Use RegisterRule to add rules to it.

func (*Linter) HasRules added in v0.2.0

func (l *Linter) HasRules(t FileType) bool

HasRules reports whether any registered rule applies to files of type t. Callers can use it to skip per-file preparation work (e.g. Feature fetches) for files no rule will inspect.

func (*Linter) LintDocument added in v0.2.0

func (l *Linter) LintDocument(path string, fileType FileType, doc *Document, dir Dir) []Issue

LintDocument applies the linter's rules to doc, a configuration file of the given type, and returns the findings sorted by position, then by rule ID and message. path is used only for reporting, and dir describes the directory containing the file; both are handed to the rules as Context.Path and Context.Dir, and dir may be left zero for an in-memory document. It reads the document as given; any mutation of its tree (see Document.Tree) must happen before calling it.

Issues that match in every field are reported once, so a rule that reaches the same problem by more than one route does not report it twice.

func (*Linter) RegisterRule

func (l *Linter) RegisterRule(r *Rule, severity Severity)

RegisterRule adds r to the linter, to run at the given severity.

type Node

type Node struct {
	// Pointer is the JSON Pointer (RFC 6901) of the value, e.g. "/image" or "/mounts/0". It is "" for
	// the document root.
	Pointer string
	// Value is the HuJSON value at Pointer.
	Value *hujson.Value
	// Arg is the "docker run" flag occurrence the value was reached as, set only on a node the
	// "runArgs" traversal produced (see [Rule.Paths]) and nil on every other node. Value is then the
	// element the flag's value is written in, which is the flag's own element or the one after it.
	Arg *dockerargs.Arg
}

Node is a single value reached by the engine's traversal.

type Platform

type Platform int

Platform identifies a target platform a rule is scoped to.

const (
	// PlatformVSCode marks a rule specific to Visual Studio Code's Dev Containers extension.
	PlatformVSCode Platform = iota
	// PlatformCodespaces marks a rule specific to GitHub Codespaces.
	PlatformCodespaces
)

func ParsePlatform

func ParsePlatform(name string) (Platform, error)

ParsePlatform parses a platform name, matched case-insensitively, into a Platform. It returns an error if name does not name a known platform.

func (Platform) MarshalJSONTo added in v0.1.0

func (p Platform) MarshalJSONTo(enc *jsontext.Encoder) error

MarshalJSONTo encodes the platform as its name (see String), for use with encoding/json/v2.

func (Platform) String

func (p Platform) String() string

String returns the platform's name, as used in the -platform flag and in output.

func (*Platform) UnmarshalJSONFrom added in v0.1.0

func (p *Platform) UnmarshalJSONFrom(dec *jsontext.Decoder) error

UnmarshalJSONFrom decodes a platform from its name (see ParsePlatform), for use with encoding/json/v2.

type Rule

type Rule struct {
	// ID is the unique identifier of the rule, used in output and in ignore directives (e.g.
	// "no-image-latest").
	ID string
	// Description is a short human-readable description of what the rule checks.
	Description string
	// LongDescription explains why the rule exists: what goes wrong in the configuration it
	// reports, and what to do instead. It is Markdown, and is shown wherever the rule is
	// documented rather than merely named.
	LongDescription string
	// References are URLs to the specification, documentation, or implementation that justify the
	// rule, most authoritative first.
	References []string
	// Category is the [Category] this rule reports; every rule must declare exactly one.
	Category Category
	// FileTypes are the kinds of configuration files this rule applies to.
	FileTypes []FileType
	// Platforms are the target platforms this rule applies to. A nil or empty value means the rule
	// applies to every platform, regardless of which platforms are selected when the linter is
	// configured.
	Platforms []Platform
	// Paths are the JSON Pointer patterns of the values this rule wants to inspect. A "*" segment
	// matches any object member name or array index (e.g. "/mounts/*"); the empty string matches the
	// document root.
	//
	// A devcontainer.json's "runArgs" is traversed as the "docker run" argv it becomes, so its
	// elements are addressed by flag rather than by index: "/runArgs/--volume" matches once per
	// occurrence of that flag, whichever spelling the argv uses, and [Node.Arg] carries the value the
	// occurrence gives it. Nothing else is addressed under it, so a pattern matching there always
	// arrives with [Node.Arg] set. Only a devcontainer.json has a "runArgs" at all: in a Feature or a
	// Template the same pattern matches whatever that name holds, an ordinary member merely spelled
	// like the flag included, with [Node.Arg] nil.
	//
	// A rule reporting a flag's absence cannot be driven by any of that, since a flag that is not
	// there is never matched; it inspects the document root instead.
	Paths []string
	// Example shows the rule firing and not firing on realistic configuration. Tests lint both: Bad
	// must report the rule, Good must not.
	Example Example
	// Check inspects one value matching Paths and returns any findings. It is called once for a given
	// value, even if several patterns match it — except for a "runArgs" element naming several flags,
	// which it is called for once per flag, with [Node.Arg] telling the occurrences apart. Check must
	// be safe for concurrent use, since it may be called for multiple files.
	Check func(ctx *Context, node *Node) []Finding
}

Rule is a single lint rule.

A rule declares the kinds of configuration files it applies to via FileTypes and the JSON Pointer paths it is interested in via Paths. The lint engine traverses the HuJSON syntax tree of each matching file exactly once and calls Check for every value matching one of its paths. The syntax tree preserves comments and byte offsets, so findings can point at the exact location of the offending value.

type Severity

type Severity int

Severity indicates how a finding should be treated: whether it's reported as an error or a warning, or not reported at all. It is specified when a rule is registered on a Linter. Severities are ordered from least to most severe, so they can be compared directly (e.g. to rank findings or apply a fail threshold).

const (
	// SeverityOff disables a rule; it produces no findings.
	SeverityOff Severity = iota
	// SeverityWarn marks a finding as a warning.
	SeverityWarn
	// SeverityError marks a finding as an error.
	SeverityError
)

func ParseSeverity

func ParseSeverity(name string) (Severity, error)

ParseSeverity parses a severity name, matched case-insensitively, into a Severity. It returns an error if name does not name a known severity.

func (Severity) MarshalJSONTo

func (s Severity) MarshalJSONTo(enc *jsontext.Encoder) error

MarshalJSONTo encodes the severity as its name (see String), for use with encoding/json/v2.

func (Severity) String

func (s Severity) String() string

String returns the severity's name, as used in output and configuration files.

func (*Severity) UnmarshalJSONFrom

func (s *Severity) UnmarshalJSONFrom(dec *jsontext.Decoder) error

UnmarshalJSONFrom decodes a severity from its name (see ParseSeverity), for use with encoding/json/v2.

type Snippet added in v0.5.0

type Snippet struct {
	// Files are the snippet's files. Exactly one must have the path a rule's first FileType is
	// named at (e.g. "devcontainer.json" for [Devcontainer]); that is the file linted, and it is
	// also the one shown first. The rest are context a rule reads from the directory, e.g. a
	// devcontainer-template.json a devcontainer.json's ${templateOption:...} reference is checked
	// against.
	Files []ExampleFile
	// DirName is the directory's own name, for a rule that reads [Dir.Name] (e.g. a Feature's or
	// Template's id must match the directory containing it). It is empty when no rule needs it.
	DirName string
}

Snippet is the directory an example is linted in: one or more files, and optionally the directory's own name, for a rule that reads it (e.g. Dir.Name).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL