1
1
package actionlint
2
2
3
3
import (
4
+ "errors"
4
5
"fmt"
5
6
"os"
6
7
"path/filepath"
8
+ "regexp"
7
9
"strings"
8
10
11
+ "github.com/bmatcuk/doublestar/v4"
9
12
"gopkg.in/yaml.v3"
10
13
)
11
14
15
+ // IgnorePatterns is a list of regular expressions. They are used for ignoring errors by matching
16
+ // to the error messages.
17
+ type IgnorePatterns []* regexp.Regexp
18
+
19
+ // Match returns whether the given error should be ignored due to the "ignore" configuration.
20
+ func (pats IgnorePatterns ) Match (err * Error ) bool {
21
+ for _ , r := range pats {
22
+ if r .MatchString (err .Message ) {
23
+ return true
24
+ }
25
+ }
26
+ return false
27
+ }
28
+
29
+ // UnmarshalYAML implements yaml.Unmarshaler.
30
+ func (pats * IgnorePatterns ) UnmarshalYAML (n * yaml.Node ) error {
31
+ if n .Kind != yaml .SequenceNode {
32
+ return fmt .Errorf ("yaml: \" ignore\" must be a sequence node at line:%d,col:%d" , n .Line , n .Column )
33
+ }
34
+ rs := make ([]* regexp.Regexp , 0 , len (n .Content ))
35
+ for _ , p := range n .Content {
36
+ r , err := regexp .Compile (p .Value )
37
+ if err != nil {
38
+ return fmt .Errorf ("invalid regular expression %q in \" ignore\" at line%d,col:%d: %w" , p .Value , n .Line , n .Column , err )
39
+ }
40
+ rs = append (rs , r )
41
+ }
42
+ * pats = rs
43
+ return nil
44
+ }
45
+
46
+ // PathConfig is a configuration for specific file path pattern. This is for values of the "paths" mapping
47
+ // in the configuration file.
48
+ type PathConfig struct {
49
+ // Ignore is a list of patterns. They are used for ignoring errors by matching to the error messages.
50
+ // It is similar to the "-ignore" command line option.
51
+ Ignore IgnorePatterns `yaml:"ignore"`
52
+ }
53
+
12
54
// Config is configuration of actionlint. This struct instance is parsed from "actionlint.yaml"
13
55
// file usually put in ".github" directory.
14
56
type Config struct {
@@ -22,13 +64,40 @@ type Config struct {
22
64
// listed here as undefined config variables.
23
65
// https://docs.github.com/en/actions/learn-github-actions/variables
24
66
ConfigVariables []string `yaml:"config-variables"`
67
+ // Paths is a "paths" mapping in the configuration file. The keys are glob patterns to match file paths.
68
+ // And the values are corresponding configurations applied to the file paths.
69
+ Paths map [string ]PathConfig `yaml:"paths"`
25
70
}
26
71
27
- func parseConfig (b []byte , path string ) (* Config , error ) {
72
+ // PathConfigsFor returns a list of all PathConfig values matching to the given file path. The path must
73
+ // be relative to the root of the project.
74
+ func (cfg * Config ) PathConfigsFor (path string ) []PathConfig {
75
+ path = filepath .ToSlash (path )
76
+
77
+ var ret []PathConfig
78
+ if cfg != nil {
79
+ for p , c := range cfg .Paths {
80
+ // Glob patterns were validated in `ParseConfig()`
81
+ if doublestar .MatchUnvalidated (p , path ) {
82
+ ret = append (ret , c )
83
+ }
84
+ }
85
+ }
86
+ return ret
87
+ }
88
+
89
+ // ParseConfig parses the given bytes as an actionlint config file. When deserializing the YAML file
90
+ // or the config validation fails, this function returns an error.
91
+ func ParseConfig (b []byte ) (* Config , error ) {
28
92
var c Config
29
93
if err := yaml .Unmarshal (b , & c ); err != nil {
30
94
msg := strings .ReplaceAll (err .Error (), "\n " , " " )
31
- return nil , fmt .Errorf ("could not parse config file %q: %s" , path , msg )
95
+ return nil , errors .New (msg )
96
+ }
97
+ for pat := range c .Paths {
98
+ if ! doublestar .ValidatePattern (pat ) {
99
+ return nil , fmt .Errorf ("invalid glob pattern %q in \" paths\" " , pat )
100
+ }
32
101
}
33
102
return & c , nil
34
103
}
@@ -39,23 +108,27 @@ func ReadConfigFile(path string) (*Config, error) {
39
108
if err != nil {
40
109
return nil , fmt .Errorf ("could not read config file %q: %w" , path , err )
41
110
}
42
- return parseConfig (b , path )
111
+ c , err := ParseConfig (b )
112
+ if err != nil {
113
+ return nil , fmt .Errorf ("could not parse config file %q: %w" , path , err )
114
+ }
115
+ return c , nil
43
116
}
44
117
45
118
// loadRepoConfig reads config file from the repository's .github/actionlint.yml or
46
119
// .github/actionlint.yaml.
47
120
func loadRepoConfig (root string ) (* Config , error ) {
48
121
for _ , f := range []string {"actionlint.yaml" , "actionlint.yml" } {
49
- path := filepath .Join (root , ".github" , f )
50
- b , err := os .ReadFile (path )
51
- if err != nil {
52
- continue // file does not exist
122
+ p := filepath .Join (root , ".github" , f )
123
+ c , err := ReadConfigFile (p )
124
+ switch {
125
+ case errors .Is (err , os .ErrNotExist ):
126
+ continue
127
+ case err != nil :
128
+ return nil , fmt .Errorf ("could not parse config file %q: %w" , p , err )
129
+ default :
130
+ return c , nil
53
131
}
54
- cfg , err := parseConfig (b , path )
55
- if err != nil {
56
- return nil , err
57
- }
58
- return cfg , nil
59
132
}
60
133
return nil , nil
61
134
}
@@ -64,10 +137,22 @@ func writeDefaultConfigFile(path string) error {
64
137
b := []byte (`self-hosted-runner:
65
138
# Labels of self-hosted runner in array of strings.
66
139
labels: []
140
+
67
141
# Configuration variables in array of strings defined in your repository or
68
142
# organization. ` + "`null`" + ` means disabling configuration variables check.
69
143
# Empty array means no configuration variable is allowed.
70
144
config-variables: null
145
+
146
+ # Configuration for file paths. The keys are glob patterns to match to file
147
+ # paths relative to the repository root. The values are the configurations for
148
+ # the file paths. Note that the path separator is always '/'.
149
+ # The following configurations are available.
150
+ #
151
+ # "ignore" is an array of regular expression patterns. Matched error messages
152
+ # are ignored. This is similar to the "-ignore" command line option.
153
+ paths:
154
+ # .github/workflows/**/*.yml:
155
+ # ignore: []
71
156
` )
72
157
if err := os .WriteFile (path , b , 0644 ); err != nil {
73
158
return fmt .Errorf ("could not write default configuration file at %q: %w" , path , err )
0 commit comments