docs Home GitHub

Your own rules

Questions you write in perch.yaml, asked in the same reading as perch's own.

A rule is a claim you make about your own code, written as a sentence and put to the model as a question. They cover what a parser cannot prove: whether a comment says why, whether a listing honors a filter, whether a test asserts something real.

They live in perch.yaml at the root of the repository.

The short form#

A rule is its name and the sentence you want held. Everything else has a default:

perch rules add no-narrative-prose --ensure "A headline and one line, not a paragraph explaining the product."
- name: no-narrative-prose
  where: "**/*"
  ensure: >-
    A headline and one line, not a paragraph explaining the product.

where defaults to **/*, the answer defaults to a yes-or-no, and the unit defaults to the file as a whole. Narrow any of them when you need to:

- name: env-read-once
  where: "src/**/*.js"
  except: "src/cli.js"
  each: method
  ensure: >
    This method does not read process.env. Reading the environment is the command
    line's job, and everything below it is passed the values.

- name: tests-assert-real-behavior
  where: "test/**/*.test.js"
  each: test
  sees: calls
  ensure: >
    Tests assert on the code under test, not on mocks they set up or values they
    built.

Your rules ride in the request perch was already making about that method, so a method covered by five rules is one reading, not six. Every question in a request is scored against the code by itself, and the code is what the request is mostly made of.

Fields#

FieldDefault
nameWhat it is called. Names the row in the table, and is what --rules and --filter kind= take.required
ensureWhat has to be true everywhere it covers.
ensure_presentSomething that has to exist somewhere in what it covers.
ensure_absentSomething that must not exist anywhere in what it covers.
whereWhat it covers: a glob, callers of <method>, or mentions <text>.**/*
exceptA glob it spares.nothing
eachfile, method, or test.the file as a whole
seesWhat a file or test is shown besides itself: file, calls, callers, or neighbors.itself
minThe floor for this rule alone, in percent.the run's --min
disabledKeeps the rule in the file without asking it.false

where#

A glob is the common case. The other two forms follow the call graph instead of the filesystem:

where: "src/**/*.js"           # a glob
where: callers of issues       # every method that calls issues()
where: mentions scan.jsonl     # every method whose source names that string

each#

each: method asks about every method separately, which is what you want when the claim is about one method's behavior. Leaving it off asks about the file as a whole, which is what you want when the claim is about how the file is arranged. each: test asks about each test function.

sees#

A method is always read with its callers and callees in view, so a method rule needs no sees. A file or a test is read alone unless you say otherwise, and a test alone cannot show whether what it asserts is real:

sees: calls        # the source of what it calls
sees: callers      # the source of what calls it
sees: neighbors    # both
sees: file         # the whole file it lives in

ensure against ensure_present and ensure_absent#

ensure has to hold everywhere, so every unit it covers is asked.

ensure_present and ensure_absent are claims about the codebase rather than about any one file, so they search the likeliest units first and stop at the answer. They cost a fraction of what a whole sweep costs.

- name: issues-closable
  where: "test/**/*.js"
  each: test
  ensure_present: >
    A test that closes an issue with a reason and then asserts it is gone from
    the default list.

- name: no-dead-command
  where: "src/**/*.js"
  each: method
  ensure_absent: >
    A command or flag that is parsed and then never used.

Writing a good one#

A rule is read by a model, so write it the way you would explain it to somebody joining the team. Say what breaks it, not only what satisfies it:

ensure: >
  methods carry a comment that tells a human reader something the code does not.
  A comment that narrates the steps below it, or restates the method's name as a
  sentence, breaks this rule.

Rewording a rule re-asks it. Leaving it alone costs nothing.

Broken rules#

A broken rule is listed with everything else, under type lint, with the rule's name in the Kind column:

$ perch issues --filter type=lint
ID        Method          Location          Type  Kind                    Severity
99d1b053  src/index.html  src/index.html:7  lint  no-narrative-prose 61%  -
1 open issue match, out of 27

perch scan exits 1 when a rule is broken. A rule is a claim you made about your own code, so CI can read that. A finding perch turned up on its own is a probability, and exiting on one would make every run a coin toss.

Editing perch.yaml from the command line#

perch rules changes the file without opening it, keeping your comments and ordering:

perch rules list
perch rules add no-stale-docs --where "docs/**/*.md" --ensure_absent "docs for code that was deleted"
perch rules edit env-read-once --except "src/cli.js,src/config.js"
perch rules remove no-stale-docs

Rewording what perch itself asks#

The questions perch ships with are written in the same grammar, in scan.yaml inside the package. A rule in perch.yaml with the same name as one of them replaces it, so you can reword a question that does not fit your codebase, or add a class of your own alongside them.

scan.yaml is worth reading once. It is the whole set of questions, and it is the clearest statement of what perch does. See the questions.

Answers that are not yes-or-no#

ensure is shorthand for a yes-or-no question. A rule can instead be written out in the grammar scan.yaml uses, which is what you need when the answer is a pick from a set or a grade against a rubric.

Field
typenoul for a probability, choice for a pick, score for a grade. Default noul.
askThe question itself, in place of ensure.
true / falseWhat a yes and a no mean, for noul.
optionsThe options and what each means, for choice.
levelsThe rubric, weakest first, for score.
whenAnother question this one is only as likely as. The two multiply.
issueWhat an answer means: type, label, on, pick, except.
perch rules add handles_absence --type choice --each method --where "src/**/*.js" \
  --ask "How does this method handle a value that is missing?" \
  --options "checks=It checks for it; ignores=It carries on with the missing value" \
  --issue "type=defect,label=handles_absence,except=checks"

when is how the scan's own security classes are gated on exposed: a class that only matters if something from outside reaches the method is written when: exposed, and its probability is multiplied by that one's. See the questions.

Edit this page on GitHub