lintp / common-patterns

This document defines reusable patterns that are used throughout the lintp documentation examples.

common-patterns/
├── standard-naming-patterns
├── framework-specific-patterns
├── validation-patterns
├── location-based-architecture
└── usage-in-configuration

standard-naming-patterns/

Case Conventions
yaml
# kebab-case: lowercase with hyphens
kebab-case: "matches($BASENAME, /^[a-z0-9]+(?:-[a-z0-9]+)*$/)"

# PascalCase: uppercase first letter, camelCase thereafter
PascalCase: "matches($BASENAME, /^[A-Z][a-zA-Z0-9]*$/)"

# camelCase: lowercase first letter, PascalCase thereafter
camelCase: "matches($BASENAME, /^[a-z][a-zA-Z0-9]*$/)"

# snake_case: lowercase with underscores
snake_case: "matches($BASENAME, /^[a-z0-9]+(?:_[a-z0-9]+)*$/)"
File Type Patterns
yaml
# JavaScript/TypeScript files
js-file: '$EXT == "js"'
ts-file: '$EXT == "ts"'
jsx-file: '$EXT == "jsx"'
tsx-file: '$EXT == "tsx"'

# Script files (any JS/TS variant)
script-file: 'in($EXT, ["js", "ts", "jsx", "tsx"])'

# Test files
test-file: 'matches($NAME, /\.test\.(js|ts|jsx|tsx)$/)'
spec-file: 'matches($NAME, /\.spec\.(js|ts|jsx|tsx)$/)'

# Config files
config-file: 'matches($NAME, /\.config\.(js|ts|json)$/)'

framework-specific-patterns/

React Patterns
yaml
# React component files
react-component: 'in($EXT, ["tsx", "jsx"]) && PascalCase'

# React hooks (must start with "use")
react-hook: 'startsWith($BASENAME, "use") && camelCase'

# Story files for Storybook — ".stories" chains onto $BASENAME (only the
# final extension is stripped), so PascalCase is checked against the stem
# with the ".stories" suffix removed.
story-file: 'endsWith($BASENAME, ".stories") && matches(without($BASENAME, ".stories"), /^[A-Z][a-zA-Z0-9]*$/)'
Node.js Backend Patterns
yaml
# API route files
route-file: 'contains($PATH, "/routes/") && kebab-case'

# Controller files
controller-file: 'endsWith($BASENAME, "Controller") && PascalCase'

# Model files
model-file: 'contains($PATH, "/models/") && PascalCase'

# Middleware files
middleware-file: 'endsWith($BASENAME, "Middleware") && PascalCase'

# Utility files
util-file: 'contains($PATH, "/utils/") && kebab-case'

validation-patterns/

Structure Requirements
yaml
# Has corresponding test file
has-test: 'any(siblings("*.test.*"), contains($item, $BASENAME))'

# Has package.json (for packages)
is-package: 'exists("package.json")'

# Has README documentation
has-docs: 'exists("README.*")'

# Has proper project structure
has-src: 'exists("src/")'
Security Patterns
yaml
# No sensitive information in filenames
no-secrets: '!contains($NAME, "secret") &&
             !contains($NAME, "password") &&
             !contains($NAME, "key") &&
             !contains($NAME, "token")'

# No temporary files
no-temp-files: '!endsWith($NAME, ".tmp") &&
                !endsWith($NAME, ".bak") &&
                !startsWith($BASENAME, "~")'

# Approved file extensions only
safe-extensions: 'in($EXT, [
  "js", "ts", "jsx", "tsx", "vue",
  "json", "yml", "yaml", "toml",
  "md", "txt", "html", "css", "scss",
  "png", "jpg", "jpeg", "gif", "svg"
])'

location-based-architecture/

For repos where each directory has its own convention, structure the config as default-deny plus directory scopes instead of one rule with many $PARENT == branches:

yaml
lintp:
  custom-matchers:
    camelCase: "matches($BASENAME, /^[a-z][a-zA-Z0-9]*$/)"
    # a source file must have a same-basename test next to it
    # (exists() patterns are relative to the file's directory)
    has-sibling-test: 'exists("${$BASENAME}.test.ts")'

  config:
    .test.ts: "camelCase || true"
    .ts: "false" # not listed below → not allowed

    "src/ecs/systems/*":
      .ts:
        rule: 'endsWith($BASENAME, "System") && camelCase && has-sibling-test'
        message: "systems are camelCase, end in System, and need a sibling test"

    "src/hooks/*":
      .ts:
        rule: "matches($BASENAME, /^use[A-Z][a-zA-Z0-9]*$/)"
        message: "hooks are named useX"

    "src/constants/*":
      .ts:
        rule: 'in($BASENAME, ["character", "inventory", "combat", "settings"])'
        message: "constants files are named after a game domain"

Three idioms doing the work:

When several file types share one convention, group their keys instead of repeating the rule:

yaml
"src/assets/images/*":
  ".{png,jpg,jpeg,gif,webp,svg}":
    rule: "camelCase"
    message: "image files are camelCase"

For directory sets, the same shape applies to .dir:

yaml
"src/*":
  .dir:
    # "*" crosses "/", so this scope also sees nested directories —
    # the $PARENT guard applies the allowlist only to src/'s direct children
    rule: '!endsWith($PARENT, "/src") || in($BASENAME, ["ecs", "hooks", "constants", "ui"])'
    message: "new src/ directories must be added to lintp.yml"

usage-in-configuration/

To use these patterns in your lintp.yml, simply reference them in your custom-matchers section:

yaml
lintp:
  custom-matchers:
    # Import patterns you need
    kebab-case: "matches($BASENAME, /^[a-z0-9]+(?:-[a-z0-9]+)*$/)"
    PascalCase: "matches($BASENAME, /^[A-Z][a-zA-Z0-9]*$/)"
    js-file: '$EXT == "js"'
    react-component: 'in($EXT, ["tsx", "jsx"]) && PascalCase'

    # Define your specific rules
    my-custom-rule: "kebab-case && js-file"

  config:
    .js: "my-custom-rule"
    .tsx: "react-component"
continue/
├── getting-started # install, CLI, config structure
├── dsl-reference   # every operator, variable, function
└── examples        # React, Node API, monorepo configs
MIT License github.com/narehart/lintp