mirror of
https://github.com/koalaman/shellcheck.git
synced 2025-10-03 19:29:44 +08:00
Fix caps, hyphenation, and highlight Haskell code-blocks
47
DevGuide.md
47
DevGuide.md
@@ -4,13 +4,13 @@ Want to write a new test? (as opposed to an [[Integration]] with an editor or C
|
|||||||
|
|
||||||
Some familiarity with Haskell helps. Most checks just use pattern matching and function calls. Grokking monads is generally not required, but `do` notation may come in handy.
|
Some familiarity with Haskell helps. Most checks just use pattern matching and function calls. Grokking monads is generally not required, but `do` notation may come in handy.
|
||||||
|
|
||||||
Feel free to skip ahead to "ShellCheck in Practice".
|
Feel free to skip ahead to [ShellCheck in practice](#shellcheck-in-practice).
|
||||||
|
|
||||||
## ShellCheck Wiki policy
|
## ShellCheck wiki policy
|
||||||
|
|
||||||
The ShellCheck Wiki can be edited by anyone with a GitHub account. Feel free to update it with special cases and additional information. If you are making a significant edit and would like someone to double check it, you can file an issue with the title `[Wiki] Updated SC1234 to ...` (and point to this paragraph since this suggestion is still new).
|
The ShellCheck wiki can be edited by anyone with a GitHub account. Feel free to update it with special cases and additional information. If you are making a significant edit and would like someone to double check it, you can file an issue with the title `[Wiki] Updated SC1234 to ...` (and point to this paragraph since this suggestion is still new).
|
||||||
|
|
||||||
## ShellCheck Theory
|
## ShellCheck theory
|
||||||
|
|
||||||
Here's the basic flow of code through ShellCheck:
|
Here's the basic flow of code through ShellCheck:
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ when (hasLeftSpace || hasRightSpace) $
|
|||||||
parseNoteAt pos ErrorC 1068 "Don't put spaces around the = in assignments."
|
parseNoteAt pos ErrorC 1068 "Don't put spaces around the = in assignments."
|
||||||
```
|
```
|
||||||
|
|
||||||
On the other hand, problems are always emitted, even when parsing fails (they are stored in a StateT higher than Parsec in the transformer stack). For example, a problem is emitted if there's an unescaped linefeed in a `[ .. ]` expression, because the statement is likely malformed or unterminated, and we want to show this warning even if we're unable to parse the whole thing:
|
On the other hand, problems are always emitted, even when parsing fails (they are stored in a `StateT` higher than Parsec in the transformer stack). For example, a problem is emitted if there's an unescaped linefeed in a `[ .. ]` expression, because the statement is likely malformed or unterminated, and we want to show this warning even if we're unable to parse the whole thing:
|
||||||
|
|
||||||
```haskell
|
```haskell
|
||||||
when (single && '\n' `elem` space) $
|
when (single && '\n' `elem` space) $
|
||||||
@@ -46,15 +46,16 @@ There's a distinction because often you can emit useful information even when pa
|
|||||||
|
|
||||||
Here are the full types of the parser:
|
Here are the full types of the parser:
|
||||||
|
|
||||||
v-- Read real/mocked files v-- Stores parse problems
|
```haskell
|
||||||
type SCBase m = Mr.ReaderT (SystemInterface m) (Ms.StateT SystemState m)
|
-- v-- Read real/mocked files v-- Stores parse problems
|
||||||
type SCParser m v = ParsecT String UserState (SCBase m) v
|
type SCBase m = Mr.ReaderT (SystemInterface m) (Ms.StateT SystemState m)
|
||||||
^-- Stores parse notes and token offsets
|
type SCParser m v = ParsecT String UserState (SCBase m) v
|
||||||
|
-- ^-- Stores parse notes and token offsets
|
||||||
|
```
|
||||||
|
|
||||||
|
### AST analysis
|
||||||
|
|
||||||
### AST Analysis
|
AST analysis comes in two primary flavors: checks that run on the root node (sometimes called _"tree checks"_), and checks that run on every node (sometimes called _"node checks"_). Due to poor planning, these can't be distinguished by type because they both just take a `Token` parameter.
|
||||||
|
|
||||||
AST analysis comes in two primary flavors: checks that run on the root node (sometimes called "tree checks"), and checks that run on every node (sometimes called "node checks"). Due to poor planning, these can't be distinguished by type because they both just take a `Token` parameter.
|
|
||||||
|
|
||||||
Here's a simple check designed to run on each node, using pattern matching to find backticks:
|
Here's a simple check designed to run on each node, using pattern matching to find backticks:
|
||||||
|
|
||||||
@@ -82,9 +83,9 @@ Similarly, some checks only trigger for a certain shell. This could be done by N
|
|||||||
|
|
||||||
### Formatting
|
### Formatting
|
||||||
|
|
||||||
ShellCheck has multiple output formatters. These take parsing results and outputs them as JSON, XML or human readable output. They rarely need tweaking. Anyone looking for a different output format should consider transforming one of the existing ones (with XSLT or python or similar) rather than writing a new formatter.
|
ShellCheck has multiple output formatters. These take parsing results and outputs them as JSON, XML or human-readable output. They rarely need tweaking. Anyone looking for a different output format should consider transforming one of the existing ones (with XSLT, Python, etc) instead of writing a new formatter.
|
||||||
|
|
||||||
## ShellCheck in Practice
|
## ShellCheck in practice
|
||||||
|
|
||||||
Let's say that we have a pet peeve: people who use `tmp` as a temporary filename. We want to warn about statements like `sort file > tmp && mv tmp file`, and suggest using `mktemp` instead.
|
Let's say that we have a pet peeve: people who use `tmp` as a temporary filename. We want to warn about statements like `sort file > tmp && mv tmp file`, and suggest using `mktemp` instead.
|
||||||
|
|
||||||
@@ -127,9 +128,11 @@ Neither is very pretty, but we can see the part we're interested in:
|
|||||||
|
|
||||||
We can compare this with the definition in `AST.hs`:
|
We can compare this with the definition in `AST.hs`:
|
||||||
|
|
||||||
v-- Redirection operator (T_Greater)
|
```haskell
|
||||||
|
-- v-- Redirection operator (T_Greater)
|
||||||
| T_IoFile Id Token Token
|
| T_IoFile Id Token Token
|
||||||
^-- Filename (T_NormalWord)
|
-- ^-- Filename (T_NormalWord)
|
||||||
|
```
|
||||||
|
|
||||||
Let's just add a check to `Analytics.hs`:
|
Let's just add a check to `Analytics.hs`:
|
||||||
```haskell
|
```haskell
|
||||||
@@ -155,11 +158,15 @@ and then append `checkTmpFilename` to the list of node checks at the top of the
|
|||||||
|
|
||||||
Now we can compile and build to see the check apply:
|
Now we can compile and build to see the check apply:
|
||||||
|
|
||||||
cabal build && dist/build/shellcheck/shellcheck - <<< "sort file > tmp"
|
```sh
|
||||||
|
cabal build && dist/build/shellcheck/shellcheck - <<< "sort file > tmp"
|
||||||
|
```
|
||||||
|
|
||||||
Alternatively, we can run it in interpreted mode, which is often way faster:
|
Alternatively, we can run it in interpreted mode, which is often way faster:
|
||||||
|
|
||||||
./quickrun - <<< "sort file > tmp"
|
```sh
|
||||||
|
./quickrun - <<< "sort file > tmp"
|
||||||
|
```
|
||||||
|
|
||||||
In either case, our warning now shows up:
|
In either case, our warning now shows up:
|
||||||
|
|
||||||
@@ -170,7 +177,7 @@ sort file > tmp
|
|||||||
^-- SC9999: We found this node: T_IoFile (Id 11) (T_Greater (Id 12)) (T_NormalWord (Id 13) [T_Literal (Id 14) "tmp"])
|
^-- SC9999: We found this node: T_IoFile (Id 11) (T_Greater (Id 12)) (T_NormalWord (Id 13) [T_Literal (Id 14) "tmp"])
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we can flesh out the check. See `ASTLib.hs` and `AnalyzerLib.hs` for convenient functions to work with AST nodes, such as getting the name of an invoked command, getting a list of flags using canonical flag parsing rules, or in this case, getting the literal string of a T_NormalWord so that it doesn't matter if we use `> 'tmp'`, `> "tmp"` or `> "t"'m'p`:
|
Now we can flesh out the check. See `ASTLib.hs` and `AnalyzerLib.hs` for convenient functions to work with AST nodes, such as getting the name of an invoked command, getting a list of flags using canonical flag parsing rules, or in this case, getting the literal string of a `T_NormalWord` so that it doesn't matter if we use `> 'tmp'`, `> "tmp"` or `> "t"'m'p`:
|
||||||
|
|
||||||
|
|
||||||
```haskell
|
```haskell
|
||||||
@@ -193,4 +200,4 @@ We can run these tests with `cabal test`, or in interpreted mode with `./quickte
|
|||||||
|
|
||||||
If we wanted to submit this test, we could run `./nextnumber` which will output the next unused SC2xxx code, e.g. 2213 as of writing.
|
If we wanted to submit this test, we could run `./nextnumber` which will output the next unused SC2xxx code, e.g. 2213 as of writing.
|
||||||
|
|
||||||
We now have a completely functional test, yay!
|
We now have a completely functional test, yay!
|
||||||
|
Reference in New Issue
Block a user