Repository navigation
feat: add no-expansions-in-single-quotes rule #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
4306846
feat: add no-expansions-in-single-quotes rule
nzakas 44c169d
docs: clarify backtick matching in no-expansions-in-single-quotes
nzakas 5d34fd4
fix: add languages and dialects to no-expansions-in-single-quotes meta
nzakas 7e3383b
fix: use the shell plugin namespace and type names in no-expansions-i…
nzakas d97265e
fix: allow no-expansions-in-single-quotes with every shell language
nzakas c522fc8
chore: apply source file conventions
nzakas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # no-expansions-in-single-quotes | ||
|
|
||
| Disallow expansion-like syntax inside single quotes, where it is not expanded. | ||
|
|
||
| ## Background | ||
|
|
||
| Inside single quotes, the shell treats every character literally. Variables, command substitutions, and backticks are not expanded, so `'$HOME'` is the five characters `$HOME`, not your home directory. Writing an expansion inside single quotes is usually a mistake where double quotes were intended. | ||
|
|
||
| ## Rule Details | ||
|
|
||
| This rule warns when a single-quoted string contains text that looks like an expansion: `$name`, `${...}`, `$(...)`, or text wrapped in a pair of backticks. | ||
|
|
||
| The rule does not warn about: | ||
|
|
||
| - Positional-style references such as `$1`. These are common in `awk` and `sed` programs, where single quotes are used on purpose so the shell leaves them alone. | ||
| - A `$` followed by a digit or punctuation, such as `'costs $5'`. | ||
| - ANSI-C quoted strings (`$'...'`). | ||
|
|
||
| Examples of **incorrect** code for this rule: | ||
|
|
||
| ```bash | ||
| # eslint shell/no-expansions-in-single-quotes: "error" | ||
|
|
||
| echo 'Hello, $USER' | ||
|
|
||
| echo 'Home is ${HOME}' | ||
|
|
||
| echo 'Today is $(date)' | ||
|
|
||
| echo 'Run `make` first' | ||
| ``` | ||
|
|
||
| Examples of **correct** code for this rule: | ||
|
|
||
| ```bash | ||
| # eslint shell/no-expansions-in-single-quotes: "error" | ||
|
|
||
| echo "Hello, $USER" | ||
|
|
||
| awk '{ print $1 }' data.txt | ||
|
|
||
| echo 'That costs $5' | ||
|
|
||
| echo $'Tab:\there' | ||
| ``` | ||
|
|
||
| ## Options | ||
|
|
||
| This rule has no options. | ||
|
|
||
| ## When Not to Use It | ||
|
|
||
| Sometimes single quotes are used precisely to delay expansion, for example `trap 'rm -f $tmpfile' EXIT` or `sh -c 'echo $HOME'`. This rule is set to `"warn"` in the recommended configuration for that reason. If your scripts frequently pass shell code as strings, you can disable this rule or use a disable comment for those lines. | ||
|
|
||
| ## Prior Art | ||
|
|
||
| - [SC2016](https://www.shellcheck.net/wiki/SC2016) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /** | ||
| * @fileoverview Tests for the no-expansions-in-single-quotes rule. | ||
| * @author Claude Opus 5.5 | ||
| */ | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| // Imports | ||
| //------------------------------------------------------------------------------ | ||
|
|
||
| import { RuleTester } from "eslint"; | ||
| import { ShellLanguage } from "../languages/shell-language.js"; | ||
| import rule from "./no-expansions-in-single-quotes.js"; | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| // Tests | ||
| //------------------------------------------------------------------------------ | ||
|
|
||
| const ruleTester = new RuleTester({ | ||
| plugins: { | ||
| shell: { | ||
| meta: { namespace: "shell" }, | ||
| languages: { bash: new ShellLanguage() }, | ||
| }, | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any -- plugin shape is validated by ESLint at runtime. | ||
| } as any, | ||
| language: "shell/bash", | ||
| }); | ||
|
|
||
| ruleTester.run("no-expansions-in-single-quotes", rule as never, { | ||
| valid: [ | ||
| "echo 'plain text'", | ||
| 'echo "$var"', | ||
| // $1-style positional references are common in awk/sed programs | ||
| "awk '{print $1}' file", | ||
| // A lone dollar sign is not an expansion | ||
| "echo 'costs $5'", | ||
| // $'...' is an intentional escape string | ||
| "echo $'tab\\there'", | ||
| ], | ||
| invalid: [ | ||
| { | ||
| code: "echo '$var'", | ||
| errors: [ | ||
| { | ||
| messageId: "expansionInSingleQuotes", | ||
| line: 1, | ||
| column: 6, | ||
| endColumn: 12, | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: "echo '${var}'", | ||
| errors: [{ messageId: "expansionInSingleQuotes" }], | ||
| }, | ||
| { | ||
| code: "echo '$(pwd)'", | ||
| errors: [{ messageId: "expansionInSingleQuotes" }], | ||
| }, | ||
| { | ||
| code: "echo 'run `date` now'", | ||
| errors: [{ messageId: "expansionInSingleQuotes" }], | ||
| }, | ||
| ], | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /** | ||
| * @fileoverview Rule to flag expansion-like text inside single quotes. | ||
| * Mirrors ShellCheck SC2016. | ||
| * @author Claude Opus 5.5 | ||
| */ | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| // Imports | ||
| //------------------------------------------------------------------------------ | ||
|
|
||
| import type { ShellRuleDefinition } from "../types.js"; | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| // Helpers | ||
| //------------------------------------------------------------------------------ | ||
|
|
||
| /** | ||
| * Matches text that looks like an intended expansion: `$name`, `${...}`, | ||
| * `$(...)`, or backticks. Positional-style references such as `$1` are | ||
| * excluded because they are common in awk/sed programs. | ||
| */ | ||
| const EXPANSION_LIKE = /\$[A-Za-z_{(]|`[^`]+`/u; | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
| // Rule Definition | ||
| //------------------------------------------------------------------------------ | ||
|
|
||
| const rule: ShellRuleDefinition<{ MessageIds: "expansionInSingleQuotes" }> = { | ||
| meta: { | ||
| type: "suggestion", | ||
| languages: ["shell/bash", "shell/posix", "shell/mksh"], | ||
| docs: { | ||
| description: | ||
| "Disallow expansion-like syntax inside single quotes, where it is not expanded", | ||
| recommended: true, | ||
| dialects: ["Bash", "POSIX sh", "mksh"], | ||
| url: "https://github.com/eslint/shell/blob/main/docs/rules/no-expansions-in-single-quotes.md", | ||
| }, | ||
| schema: [], | ||
| messages: { | ||
| expansionInSingleQuotes: | ||
| "Expressions don't expand in single quotes; use double quotes for that. (ShellCheck SC2016)", | ||
| }, | ||
| }, | ||
|
|
||
| create(context) { | ||
| return { | ||
| SingleQuotedString(node) { | ||
| if (!node.dollar && EXPANSION_LIKE.test(node.value)) { | ||
| context.report({ | ||
| node, | ||
| messageId: "expansionInSingleQuotes", | ||
| }); | ||
| } | ||
| }, | ||
| }; | ||
| }, | ||
| }; | ||
|
|
||
| export default rule; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.