Skip to content
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

feat: add inline-ignore #375

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,35 @@ processes.
actionlint -shellcheck= -pyflakes=
```

### Ignore error by inline comment

To ignore an error by inline comment, use `# actionlint ignore=<pattern>` or `# actionlint ignore=<pattern 1>,<pattern 2>,...` comment in the workflow file.
The comment should be placed at the before the line where the error is reported. Ignore pattern is the same as `-ignore` option.

```yaml
# actionlint ignore=undefined variable
- run: echo '${{ foo }}'
```

```yaml
# actionlint ignore=undefined variable ".*"
- run: echo '${{ foo }}'
```

```yaml
- name: ignore shellcheck errors
# actionlint ignore=SC2016
run: |
actionlint -format '{{range $err := .}}::error file={{$err.Filepath}},line={{$err.Line}},col={{$err.Column}}::{{$err.Message}}{{end}}' .github/workflows/*.yml
```

```yaml
# actionlint ignore=undefined variable, potentially untrusted
- run: |
echo '${{ foo }}'
echo '${{ github.event.head_commit.author.name }}'
```

<a name="format"></a>
### Format error messages

Expand Down
20 changes: 19 additions & 1 deletion linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ func (l *Linter) check(
l.debug("No config was found")
}

w, all := Parse(content)
w, i, all := Parse(content)

if l.logLevel >= LogLevelVerbose {
elapsed := time.Since(start)
Expand Down Expand Up @@ -599,6 +599,24 @@ func (l *Linter) check(
all = filtered
}

// ignore inline ignore patterns
if len(i) > 0 {
filtered := make([]*Error, 0, len(all))
Loop2:
for _, err := range all {
patterns, ok := i[err.Line]
if ok {
for _, p := range patterns {
if p.MatchString(err.Message) {
continue Loop2
}
}
}
filtered = append(filtered, err)
}
all = filtered
}

for _, err := range all {
err.Filepath = path // Populate filename in the error
}
Expand Down
35 changes: 32 additions & 3 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -1402,21 +1402,50 @@ func handleYAMLError(err error) []*Error {
return []*Error{yamlErr(err.Error())}
}

var ignoreRe = regexp.MustCompile("(?:^|\n)\\s*#\\s*actionlint\\s+ignore=([^\n]+)\\s*$")

func visitNodeForIgnores(n *yaml.Node, i *map[int][]regexp.Regexp) {
if n.HeadComment != "" {
line := posAt(n).Line
matches := ignoreRe.FindAllStringSubmatch(n.HeadComment, -1)
for _, m := range matches {
if len(m) > 1 {
patterns := strings.Split(m[1], ",")
(*i)[line] = make([]regexp.Regexp, 0, len(patterns))
for _, p := range patterns {
r, err := regexp.Compile(p)
if err != nil {
continue
}
(*i)[line] = append((*i)[line], *r)
}
}
}
}
if n.Content != nil {
for _, c := range n.Content {
visitNodeForIgnores(c, i)
}
}
}

// Parse parses given source as byte sequence into workflow syntax tree. It returns all errors
// detected while parsing the input. It means that detecting one error does not stop parsing. Even
// if one or more errors are detected, parser will try to continue parsing and finding more errors.
func Parse(b []byte) (*Workflow, []*Error) {
func Parse(b []byte) (*Workflow, map[int][]regexp.Regexp, []*Error) {
var n yaml.Node

if err := yaml.Unmarshal(b, &n); err != nil {
return nil, handleYAMLError(err)
return nil, nil, handleYAMLError(err)
}

// Uncomment for checking YAML tree
// dumpYAML(&n, 0)

p := &parser{}
w := p.parse(&n)
i := map[int][]regexp.Regexp{}
visitNodeForIgnores(&n, &i)

return w, p.errors
return w, i, p.errors
}
3 changes: 3 additions & 0 deletions testdata/err/ignore_not_works.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test.yaml:14:24: "github.event.head_commit.author.name" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions for more details [expression]
test.yaml:16:24: "github.event.head_commit.author.name" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions for more details [expression]
test.yaml:18:24: "github.event.head_commit.author.name" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions for more details [expression]
20 changes: 20 additions & 0 deletions testdata/err/ignore_not_works.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
on:
push:

jobs:
actionlint:
runs-on: ubuntu-latest
steps:
# OK
# actionlint ignore=potentially untrusted
- run: echo '${{ github.event.head_commit.author.name }}'
# ERROR: ignore should placed last line
# actionlint ignore=potentially untrusted
# dummy comment
- run: echo '${{ github.event.head_commit.author.name }}'
# ERROR: not ignored
- run: echo '${{ github.event.head_commit.author.name }}' # actionlint ignore=potentially untrusted
# ERROR: not ignored
- run: echo '${{ github.event.head_commit.author.name }}'
# actionlint ignore=potentially untrusted

1 change: 1 addition & 0 deletions testdata/examples/ignore.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test.yaml:11:24: "github.event.pull_request.title" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions for more details [expression]
24 changes: 24 additions & 0 deletions testdata/examples/ignore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Lint GitHub Actions workflows
on:
push:

jobs:
actionlint:
runs-on: ubuntu-latest
steps:
# ERROR: this step contains an actionlint error
- name: Print pull request title
run: echo '${{ github.event.pull_request.title }}'
# OK: ignore error (using environment variable is more better solution)
- name: Print pull request title
# actionlint ignore=potentially untrusted
run: echo '${{ github.event.pull_request.title }}'
# ERROR: this step contains a shellcheck error
- name: Run Actionlint
run: |
actionlint -format '{{range $err := .}}::error file={{$err.Filepath}},line={{$err.Line}},col={{$err.Column}}::{{$err.Message}}{{end}}' .github/workflows/*.yml
# OK: ignore error
- name: Run Actionlint
# actionlint ignore=SC2016
run: |
actionlint -format '{{range $err := .}}::error file={{$err.Filepath}},line={{$err.Line}},col={{$err.Column}}::{{$err.Message}}{{end}}' .github/workflows/*.yml
45 changes: 45 additions & 0 deletions testdata/ok/ignore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
on:
push:

jobs:
actionlint:
runs-on: ubuntu-latest
steps:
# ignore one error
# actionlint ignore=undefined variable ".*"
- run: echo '${{ foo }}'
# multiple ignores
# actionlint ignore=undefined variable, potentially untrusted
- run: |
echo '${{ foo }}'
echo '${{ github.event.head_commit.author.name }}'
- name: ignore shellcheck errors
# actionlint ignore=SC2016
run: |
actionlint -format '{{range $err := .}}::error file={{$err.Filepath}},line={{$err.Line}},col={{$err.Column}}::{{$err.Message}}{{end}}' .github/workflows/*.yml
- name: ignore actionlint errors
# actionlint ignore=potentially untrusted
run: echo '${{ github.event.head_commit.author.name }}'
- name: ignore errors by regexp
# actionlint ignore=po.*ly un[r-u]*ed
run: echo '${{ github.event.head_commit.author.name }}'
- name: multiline command
# actionlint ignore=potentially untrusted
run: |
echo "hello"
echo '${{ github.event.head_commit.author.name }}'
echo "hello"
- name: ignore shellcheck errors in multiline command
# actionlint ignore=SC2016
run: |
echo "hello"
actionlint -format '{{range $err := .}}::error file={{$err.Filepath}},line={{$err.Line}},col={{$err.Column}}::{{$err.Message}}{{end}}' .github/workflows/*.yml
echo "hello"
# actionlint ignore=SC2016
- run: |
actionlint -format '{{range $err := .}}::error file={{$err.Filepath}},line={{$err.Line}},col={{$err.Column}}::{{$err.Message}}{{end}}' .github/workflows/*.yml
# actionlint ignore=undefined variable
- run: |
echo "hello"
echo "${{foo}}"
echo "hello"