struct

Ignorelint::Pattern

Inherits Struct < Value < Object

Constructors

new(raw : String, line : Int32)

Parse a single raw line into its semantic components.

The decomposition order matters:

  1. Strip trailing \n / \r (file read artifacts)
  2. Detect ! prefix → negated
  3. Detect trailing / → directory_only
  4. Detect leading / → rooted
  5. Strip escape backslashes → body

Each step peels off the detected flag from remaining so the next step only sees what is left.

Instance methods

blank?

True for blank (empty or whitespace-only) lines. Blank lines separate groups visually but carry no pattern meaning.

body

The semantic core of the pattern: escape sequences removed, ! / / flags stripped. This is what the linter inspects for glob validity, duplicate detection, and filesystem matching.

comment?

True for comment lines (start with # after optional whitespace). Note: in gitignore, # is only a comment marker at the start of a line; a # mid-pattern is treated as a literal character (and flagged by has_unescaped_hash?).

conflicts_with?(other : Pattern) : Bool

Does this pattern (as an ignore) conflict with a later negation? A pattern P and a negation !P cancel each other.

Scope flags participate: /build vs !build (rooted vs unrooted) and build/ vs !build (directory-only vs either) match different sets, so they are not exact cancels.

Used by Checks::Conflicts to detect redundant pairs like "build" followed by "!build".

consecutive_asterisks?

More than two consecutive asterisks (*** or more). Gitignore only recognizes * (any chars except /) and ** (any chars including /). Three or more is almost certainly a typo.

directory_only?

true when the line ends with / — the pattern only matches directories, not files. In .dockerignore this distinction is meaningless because Docker strips trailing slashes before matching.

double_negation?

Detect !! at the start of a line (after leading whitespace). Double negation cancels out — "!!foo" is equivalent to "foo".

empty_pattern?

Detect an empty pattern after decomposition (no body text). Lines like "!", "!/" or "/" carry flags but no pattern text.

has_double_slash?

Detect // in the pattern body. Double slashes usually indicate a typo (e.g. "src//dist" instead of "src/dist"). Gitignore does treat // as a single /, so this is informational, not an error.

has_leading_whitespace?

Detect leading spaces or tabs before the pattern. In gitignore, leading whitespace is not stripped — the pattern " foo" does not match "foo".

has_trailing_whitespace?

Detect trailing spaces or tabs that are invisible in most editors but affect matching behavior. The \ (backslash-space) escape is excluded because it is an intentional escaped space, not accidental whitespace.

has_unescaped_hash?

Detect an unescaped # inside the pattern body.

In gitignore, # starts a comment only at the beginning of a line. A # mid-pattern (e.g. "file#1.txt") is technically a literal, but most users do not realize this and may intend it as a comment delimiter. The check skips lines that are already comments.

The algorithm walks character-by-character tracking the escape state:

  • \\ toggles in_escape
  • # while not escaped → unescaped hash found
  • any other character resets in_escape
invalid_doublestar?

** used incorrectly: not between / separators.

In gitignore, ** is only valid in these positions:

  • a/**/b — matches zero or more directories between a and b
  • **/a — matches a at any depth
  • a/** — matches everything inside a/

Invalid: a**b, **a, a** (not at segment boundary).

The check splits the pattern on / and ensures any segment containing ** is exactly "**" — no extra characters.

line

1-based line number in the source file. Used to report issue locations.

literal?

True when the body contains no UNESCAPED glob metacharacters (*, ?, [) and the pattern is not negated — i.e. it names a concrete path.

Scans the raw line (skipping \-escaped chars) so an escaped \* counts as a literal asterisk, not a glob.

Used by filesystem checks to decide whether to test File.exists? (for literal patterns) vs. Dir.glob (for glob patterns).

malformed_brackets?

Malformed bracket expression: unclosed [ or empty [].

Bracket expressions like [abc] match a single character from the set. Common mistakes:

  • Unclosed: [abc — missing closing bracket
  • Empty: [] — no characters to match
  • Nested: [a[b] — brackets inside brackets

Runs on the raw line with escape awareness: an escaped \[ is a literal bracket, not an expression opener.

negated?

true when the line starts with ! — a negation pattern that re-includes previously excluded files. Not all ignore formats support negation (e.g. .slugignore does not).

negated_rooted?

! followed by / — anchoring has no effect on negation.

In gitignore, a leading / anchors a pattern to the ignore file's directory. But negation patterns (!) are already resolved relative to that directory, so adding / to a negation is misleading.

normalized

Normalized form for duplicate/conflict comparison: escape-stripped body, trimmed. Case-sensitive: ignore files are (e.g. "Build" and "build" match different files on case-sensitive filesystems).

raw

The original raw text from the file (with leading ! and trailing / intact). Used in error messages so the user sees exactly what they wrote.

rooted?

true when the pattern starts with / (after removing !) — it is "rooted" / "anchored" to the directory containing the ignore file. Without a leading /, the pattern matches at any depth.

rooted_shallow?

Pattern contains a leading slash that anchors to root but is followed by no path separators — it only matches at the top level. Often a mistake when the user wanted to match anywhere.

Example: "/build" matches only ./build, not ./src/build. The user likely meant "build" (matches at any depth).