Skip to content

Commit e10484a

Browse files
committed
handle linting stdin by Linter.LintStdin instead of Command
1 parent 5efb940 commit e10484a

File tree

3 files changed

+78
-13
lines changed

3 files changed

+78
-13
lines changed

command.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,7 @@ func (cmd *Command) runLinter(args []string, opts *LinterOptions, initConfig boo
102102
}
103103

104104
if len(args) == 1 && args[0] == "-" {
105-
b, err := io.ReadAll(cmd.Stdin)
106-
if err != nil {
107-
return nil, fmt.Errorf("could not read stdin: %w", err)
108-
}
109-
n := "<stdin>"
110-
if opts.StdinFileName != "" {
111-
n = opts.StdinFileName
112-
}
113-
return l.Lint(n, b, nil)
105+
return l.LintStdin(cmd.Stdin)
114106
}
115107

116108
return l.LintFiles(args, nil)
@@ -151,7 +143,7 @@ func (cmd *Command) Main(args []string) int {
151143
flags.BoolVar(&opts.Verbose, "verbose", false, "Enable verbose output")
152144
flags.BoolVar(&opts.Debug, "debug", false, "Enable debug output (for development)")
153145
flags.BoolVar(&ver, "version", false, "Show version and how this binary was installed")
154-
flags.StringVar(&opts.StdinFileName, "stdin-filename", "", "File name when reading input from stdin")
146+
flags.StringVar(&opts.StdinFileName, "stdin-filename", "<stdin>", "File name when reading input from stdin")
155147
flags.Usage = func() {
156148
printUsageHeader(cmd.Stderr)
157149
flags.PrintDefaults()

linter.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ type Linter struct {
103103
shellcheck string
104104
pyflakes string
105105
ignorePats []*regexp.Regexp
106+
stdin string
106107
defaultConfig *Config
107108
errFmt *ErrorFormatter
108109
cwd string
@@ -172,6 +173,11 @@ func NewLinter(out io.Writer, opts *LinterOptions) (*Linter, error) {
172173
}
173174
}
174175

176+
stdin := "<stdin>"
177+
if opts.StdinFileName != "" {
178+
stdin = opts.StdinFileName
179+
}
180+
175181
return &Linter{
176182
NewProjects(),
177183
out,
@@ -181,6 +187,7 @@ func NewLinter(out io.Writer, opts *LinterOptions) (*Linter, error) {
181187
opts.Shellcheck,
182188
opts.Pyflakes,
183189
ignore,
190+
stdin,
184191
cfg,
185192
formatter,
186193
cwd,
@@ -443,9 +450,20 @@ func (l *Linter) LintFile(path string, project *Project) ([]*Error, error) {
443450
return errs, err
444451
}
445452

453+
// LintStdin lints the content read from STDIN. The stdin parameter is a reader to read from STDIN,
454+
// which is usually os.Stdin. The file name is determined by LinterOptions.StdinFileName. When the
455+
// option is empty, "<stdin>" is the default value.
456+
func (l *Linter) LintStdin(stdin io.Reader) ([]*Error, error) {
457+
l.log("Reading the input from stdin")
458+
b, err := io.ReadAll(stdin)
459+
if err != nil {
460+
return nil, fmt.Errorf("could not read stdin: %w", err)
461+
}
462+
return l.Lint(l.stdin, b, nil)
463+
}
464+
446465
// Lint lints YAML workflow file content given as byte slice. The path parameter is used as file
447-
// path where the content came from. Setting "<stdin>" to path parameter indicates the output came
448-
// from STDIN.
466+
// path where the content came from.
449467
// When nil is passed to the project parameter, it tries to find the project from the path parameter.
450468
func (l *Linter) Lint(path string, content []byte, project *Project) ([]*Error, error) {
451469
if project == nil && path != "<stdin>" {

linter_test.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package actionlint
22

33
import (
44
"bufio"
5+
"bytes"
56
"encoding/json"
7+
"errors"
68
"fmt"
79
"io"
810
"os"
@@ -17,6 +19,12 @@ import (
1719
"golang.org/x/sys/execabs"
1820
)
1921

22+
type testErrorReader struct{}
23+
24+
func (r testErrorReader) Read(p []byte) (int, error) {
25+
return 0, errors.New("dummy read error")
26+
}
27+
2028
func TestLinterLintOK(t *testing.T) {
2129
dir := filepath.Join("testdata", "ok")
2230

@@ -432,7 +440,7 @@ func TestLinterFormatErrorMessageInSARIF(t *testing.T) {
432440

433441
var have interface{}
434442
if err := json.Unmarshal([]byte(out), &have); err != nil {
435-
t.Fatalf("Output is not JSON: %v: %q", err, out)
443+
t.Fatalf("output is not JSON: %v: %q", err, out)
436444
}
437445

438446
bytes, err = os.ReadFile(filepath.Join(dir, "test.sarif"))
@@ -449,6 +457,53 @@ func TestLinterFormatErrorMessageInSARIF(t *testing.T) {
449457
}
450458
}
451459

460+
func TestLinterLintStdinOK(t *testing.T) {
461+
for _, f := range []string{"", "foo.yaml"} {
462+
l, err := NewLinter(io.Discard, &LinterOptions{StdinFileName: f})
463+
if err != nil {
464+
t.Fatalf("creating Linter object with stdin file name %q caused error: %v", f, err)
465+
}
466+
467+
in := []byte(`on: push
468+
jobs:
469+
job:
470+
runs-on: foo
471+
steps:
472+
- run: echo`)
473+
errs, err := l.LintStdin(bytes.NewReader(in))
474+
if err != nil {
475+
t.Fatalf("linting input with stdin file name %q caused error: %v", f, err)
476+
}
477+
if len(errs) != 1 {
478+
t.Fatalf("unexpected number of errors with stdin file name %q: %v", f, errs)
479+
}
480+
481+
want := f
482+
if want == "" {
483+
want = "<stdin>"
484+
}
485+
if errs[0].Filepath != want {
486+
t.Fatalf("file path in the error with stdin file name %q should be %q but got %q", f, want, errs[0].Filepath)
487+
}
488+
}
489+
}
490+
491+
func TestLinterLintStdinReadError(t *testing.T) {
492+
l, err := NewLinter(io.Discard, &LinterOptions{})
493+
if err != nil {
494+
t.Fatal(err)
495+
}
496+
_, err = l.LintStdin(testErrorReader{})
497+
if err == nil {
498+
t.Fatal("error did not occur")
499+
}
500+
want := "could not read stdin: dummy read error"
501+
have := err.Error()
502+
if want != have {
503+
t.Fatalf("wanted error message %q but have %q", want, have)
504+
}
505+
}
506+
452507
func TestLinterPathsNotFound(t *testing.T) {
453508
l, err := NewLinter(io.Discard, &LinterOptions{})
454509
if err != nil {

0 commit comments

Comments
 (0)