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

Changes to make actionlint usable when using it as a library #327

Merged
merged 4 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 3 additions & 3 deletions linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ func (l *Linter) LintFiles(filepaths []string, project *Project) ([]*Error, erro
})
}

proc.wait()
proc.Wait()
if err := eg.Wait(); err != nil {
return nil, err
}
Expand Down Expand Up @@ -401,7 +401,7 @@ func (l *Linter) LintFile(path string, project *Project) ([]*Error, error) {
localActions := NewLocalActionsCache(project, dbg)
localReusableWorkflows := NewLocalReusableWorkflowCache(project, l.cwd, dbg)
errs, err := l.check(path, src, project, proc, localActions, localReusableWorkflows)
proc.wait()
proc.Wait()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -429,7 +429,7 @@ func (l *Linter) Lint(path string, content []byte, project *Project) ([]*Error,
localActions := NewLocalActionsCache(project, dbg)
localReusableWorkflows := NewLocalReusableWorkflowCache(project, l.cwd, dbg)
errs, err := l.check(path, content, project, proc, localActions, localReusableWorkflows)
proc.wait()
proc.Wait()
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion process.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (proc *ConcurrentProcess) run(eg *errgroup.Group, exe string, args []string
}

// wait waits all goroutines started by this concurrentProcess instance finish.
hugo-syn marked this conversation as resolved.
Show resolved Hide resolved
func (proc *ConcurrentProcess) wait() {
func (proc *ConcurrentProcess) Wait() {
proc.wg.Wait() // Wait for all goroutines completing to shutdown
}

Expand Down
18 changes: 9 additions & 9 deletions process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestProcessRunProcessSerial(t *testing.T) {
if err := echo.wait(); err != nil {
t.Fatal(err)
}
p.wait()
p.Wait()

if len(ret) != numProcs {
t.Fatalf("wanted %d outputs but got %#v", numProcs, ret)
Expand Down Expand Up @@ -110,7 +110,7 @@ func TestProcessRunConcurrently(t *testing.T) {
if err := sleep.wait(); err != nil {
t.Fatal(err)
}
p.wait()
p.Wait()

sec := time.Since(start).Seconds()
if sec >= 0.5 {
Expand Down Expand Up @@ -167,7 +167,7 @@ func TestProcessWaitMultipleCommandsFinish(t *testing.T) {
})
}

p.wait()
p.Wait()

for i, b := range done {
if !b {
Expand All @@ -193,7 +193,7 @@ func TestProcessInputStdin(t *testing.T) {
if err := cat.wait(); err != nil {
t.Fatal(err)
}
p.wait()
p.Wait()

if out != "this is test" {
t.Fatalf("stdin was not input to `cat` command: %q", out)
Expand All @@ -220,10 +220,10 @@ func TestProcessErrorCommandNotFound(t *testing.T) {

err := c.wait()
if err == nil || !strings.Contains(err.Error(), "yay! error found!") {
t.Fatalf("error was not reported by p.wait(): %v", err)
t.Fatalf("error was not reported by p.Wait(): %v", err)
}

p.wait()
p.Wait()

if !echoDone {
t.Fatal("a command following the error did not run")
Expand All @@ -247,10 +247,10 @@ func TestProcessErrorInCallback(t *testing.T) {

err := echo.wait()
if err == nil || err.Error() != "dummy error" {
t.Fatalf("error was not reported by p.wait(): %v", err)
t.Fatalf("error was not reported by p.Wait(): %v", err)
}

p.wait()
p.Wait()

if !echoDone {
t.Fatal("a command following the error did not run")
Expand Down Expand Up @@ -284,7 +284,7 @@ func TestProcessErrorLinterFailed(t *testing.T) {
t.Fatalf("Error message was unexpected: %q", msg)
}

p.wait()
p.Wait()

if !echoDone {
t.Fatal("a command following the error did not run")
Expand Down
11 changes: 9 additions & 2 deletions rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ type RuleBase struct {
config *Config
}

func NewRuleBase(name string, desc string) *RuleBase {
return &RuleBase{
name: name,
desc: desc,
}
}

// VisitStep is callback when visiting Step node.
func (r *RuleBase) VisitStep(node *Step) error { return nil }

Expand All @@ -30,12 +37,12 @@ func (r *RuleBase) VisitWorkflowPre(node *Workflow) error { return nil }
// VisitWorkflowPost is callback when visiting Workflow node after visiting its children.
func (r *RuleBase) VisitWorkflowPost(node *Workflow) error { return nil }

func (r *RuleBase) error(pos *Pos, msg string) {
func (r *RuleBase) Error(pos *Pos, msg string) {
err := errorAt(pos, r.name, msg)
r.errs = append(r.errs, err)
}

func (r *RuleBase) errorf(pos *Pos, format string, args ...interface{}) {
func (r *RuleBase) Errorf(pos *Pos, format string, args ...interface{}) {
err := errorfAt(pos, r.name, format, args...)
r.errs = append(r.errs, err)
}
Expand Down
12 changes: 6 additions & 6 deletions rule_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (rule *RuleAction) checkRepoAction(spec string, exec *ExecAction) {
}

func (rule *RuleAction) invalidActionFormat(pos *Pos, spec string, why string) {
rule.errorf(pos, "specifying action %q in invalid format because %s. available formats are \"{owner}/{repo}@{ref}\" or \"{owner}/{repo}/{path}@{ref}\"", spec, why)
rule.Errorf(pos, "specifying action %q in invalid format because %s. available formats are \"{owner}/{repo}@{ref}\" or \"{owner}/{repo}/{path}@{ref}\"", spec, why)
}

// https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#example-using-the-github-packages-container-registry
Expand All @@ -116,7 +116,7 @@ func (rule *RuleAction) checkDockerAction(uri string, exec *ExecAction) {
}

if _, err := url.Parse(uri); err != nil {
rule.errorf(
rule.Errorf(
exec.Uses.Pos,
"URI for Docker container %q is invalid: %s (tag=%s)",
uri,
Expand All @@ -126,15 +126,15 @@ func (rule *RuleAction) checkDockerAction(uri string, exec *ExecAction) {
}

if tagExists && tag == "" {
rule.errorf(exec.Uses.Pos, "tag of Docker action should not be empty: %q", uri)
rule.Errorf(exec.Uses.Pos, "tag of Docker action should not be empty: %q", uri)
}
}

// https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#example-using-action-in-the-same-repository-as-the-workflow
func (rule *RuleAction) checkLocalAction(path string, action *ExecAction) {
meta, err := rule.cache.FindMetadata(path)
if err != nil {
rule.error(action.Uses.Pos, err.Error())
rule.Error(action.Uses.Pos, err.Error())
return
}
if meta == nil {
Expand All @@ -154,7 +154,7 @@ func (rule *RuleAction) checkAction(meta *ActionMetadata, exec *ExecAction, desc
for _, i := range meta.Inputs {
ns = append(ns, i.Name)
}
rule.errorf(
rule.Errorf(
i.Name.Pos,
"input %q is not defined in action %s. available inputs are %s",
i.Name.Value,
Expand All @@ -174,7 +174,7 @@ func (rule *RuleAction) checkAction(meta *ActionMetadata, exec *ExecAction, desc
ns = append(ns, i.Name)
}
}
rule.errorf(
rule.Errorf(
exec.Uses.Pos,
"missing input %q which is required by action %s. all required inputs are %s",
i.Name,
Expand Down
2 changes: 1 addition & 1 deletion rule_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ func (rule *RuleCredentials) checkContainer(where string, n *Container) {

p := n.Credentials.Password
if !p.IsExpressionAssigned() {
rule.errorf(p.Pos, "\"password\" section in %s should be specified via secrets. do not put password value directly", where)
rule.Errorf(p.Pos, "\"password\" section in %s should be specified via secrets. do not put password value directly", where)
}
}
2 changes: 1 addition & 1 deletion rule_deprecated_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (rule *RuleDeprecatedCommands) VisitStep(n *Step) error {
panic("unreachable")
}

rule.errorf(
rule.Errorf(
r.Run.Pos,
"workflow command %q was deprecated. use `%s` instead: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions",
c,
Expand Down
2 changes: 1 addition & 1 deletion rule_env_var.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (rule *RuleEnvVar) checkEnv(env *Env) {
continue // Key name can contain expressions (#312)
}
if strings.ContainsAny(v.Name.Value, "&= ") {
rule.errorf(
rule.Errorf(
v.Name.Pos,
"environment variable name %q is invalid. '&', '=' and spaces should not be contained",
v.Name.Value,
Expand Down
38 changes: 19 additions & 19 deletions rule_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (rule *RuleEvents) checkCron(spec *String) {
p := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
sched, err := p.Parse(spec.Value)
if err != nil {
rule.errorf(spec.Pos, "invalid CRON format %q in schedule event: %s", spec.Value, err.Error())
rule.Errorf(spec.Pos, "invalid CRON format %q in schedule event: %s", spec.Value, err.Error())
return
}

Expand All @@ -70,7 +70,7 @@ func (rule *RuleEvents) checkCron(spec *String) {
//
// > The shortest interval you can run scheduled workflows is once every 5 minutes.
if diff < 60.0*5 {
rule.errorf(spec.Pos, "scheduled job runs too frequently. it runs once per %g seconds. the shortest interval is once every 5 minutes", diff)
rule.Errorf(spec.Pos, "scheduled job runs too frequently. it runs once per %g seconds. the shortest interval is once every 5 minutes", diff)
}
}

Expand All @@ -79,7 +79,7 @@ func (rule *RuleEvents) filterNotAvailable(pos *Pos, filter, hook string, availa
if len(available) < 2 {
e = "event"
}
rule.errorf(pos, "%q filter is not available for %s event. it is only for %s %s", filter, hook, strings.Join(available, ", "), e)
rule.Errorf(pos, "%q filter is not available for %s event. it is only for %s %s", filter, hook, strings.Join(available, ", "), e)
}

func (rule *RuleEvents) checkExclusiveFilters(filter, ignore *WebhookEventFilter, hook string, available []string) {
Expand All @@ -97,7 +97,7 @@ func (rule *RuleEvents) checkExclusiveFilters(filter, ignore *WebhookEventFilter
if p.IsBefore(ignore.Name.Pos) {
p = ignore.Name.Pos
}
rule.errorf(p, "both %q and %q filters cannot be used for the same event %q. note: use '!' to negate patterns", filter.Name.Value, ignore.Name.Value, hook)
rule.Errorf(p, "both %q and %q filters cannot be used for the same event %q. note: use '!' to negate patterns", filter.Name.Value, ignore.Name.Value, hook)
}
} else {
if !filter.IsEmpty() {
Expand All @@ -115,19 +115,19 @@ func (rule *RuleEvents) checkWebhookEvent(event *WebhookEvent) {

types, ok := AllWebhookTypes[hook]
if !ok {
rule.errorf(event.Pos, "unknown Webhook event %q. see https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#webhook-events for list of all Webhook event names", hook)
rule.Errorf(event.Pos, "unknown Webhook event %q. see https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#webhook-events for list of all Webhook event names", hook)
return
}

rule.checkTypes(event.Hook, event.Types, types)

if hook == "workflow_run" {
if len(event.Workflows) == 0 {
rule.error(event.Pos, "no workflow is configured for \"workflow_run\" event")
rule.Error(event.Pos, "no workflow is configured for \"workflow_run\" event")
}
} else {
if len(event.Workflows) != 0 {
rule.errorf(event.Pos, "\"workflows\" cannot be configured for %q event. it is only for workflow_run event", hook)
rule.Errorf(event.Pos, "\"workflows\" cannot be configured for %q event. it is only for workflow_run event", hook)
}
}

Expand Down Expand Up @@ -158,7 +158,7 @@ func (rule *RuleEvents) checkWebhookEvent(event *WebhookEvent) {

func (rule *RuleEvents) checkTypes(hook *String, types []*String, expected []string) {
if len(expected) == 0 && len(types) > 0 {
rule.errorf(hook.Pos, "\"types\" cannot be specified for %q Webhook event", hook.Value)
rule.Errorf(hook.Pos, "\"types\" cannot be specified for %q Webhook event", hook.Value)
return
}

Expand All @@ -171,7 +171,7 @@ func (rule *RuleEvents) checkTypes(hook *String, types []*String, expected []str
}
}
if !valid {
rule.errorf(
rule.Errorf(
ty.Pos,
"invalid activity type %q for %q Webhook event. available types are %s",
ty.Value,
Expand All @@ -193,7 +193,7 @@ func (rule *RuleEvents) checkWorkflowCallEvent(event *WorkflowCallEvent) {
switch i.Type {
case WorkflowCallEventInputTypeNumber:
if _, err := strconv.ParseFloat(i.Default.Value, 64); err != nil {
rule.errorf(
rule.Errorf(
i.Default.Pos,
"input of workflow_call event %q is typed as number but its default value %q cannot be parsed as a float number: %s",
i.Name.Value,
Expand All @@ -203,7 +203,7 @@ func (rule *RuleEvents) checkWorkflowCallEvent(event *WorkflowCallEvent) {
}
case WorkflowCallEventInputTypeBoolean:
if d := strings.ToLower(i.Default.Value); d != "true" && d != "false" {
rule.errorf(
rule.Errorf(
i.Default.Pos,
"input of workflow_call event %q is typed as boolean. its default value must be true or false but got %q",
i.Name.Value,
Expand All @@ -213,7 +213,7 @@ func (rule *RuleEvents) checkWorkflowCallEvent(event *WorkflowCallEvent) {
}
}
if i.IsRequired() {
rule.errorf(
rule.Errorf(
i.Default.Pos,
"input %q of workflow_call event has the default value %q, but it is also required. if an input is marked as required, its default value will never be used",
i.Name.Value,
Expand All @@ -229,13 +229,13 @@ func (rule *RuleEvents) checkWorkflowDispatchEvent(event *WorkflowDispatchEvent)
for n, i := range event.Inputs {
if i.Type == WorkflowDispatchEventInputTypeChoice {
if len(i.Options) == 0 {
rule.errorf(i.Name.Pos, "input type of %q is \"choice\" but \"options\" is not set", n)
rule.Errorf(i.Name.Pos, "input type of %q is \"choice\" but \"options\" is not set", n)
continue
}
seen := make(map[string]struct{}, len(i.Options))
for _, o := range i.Options {
if _, ok := seen[o.Value]; ok {
rule.errorf(o.Pos, "option %q is duplicated in options of %q input", o.Value, n)
rule.Errorf(o.Pos, "option %q is duplicated in options of %q input", o.Value, n)
continue
}
seen[o.Value] = struct{}{}
Expand All @@ -246,20 +246,20 @@ func (rule *RuleEvents) checkWorkflowDispatchEvent(event *WorkflowDispatchEvent)
b.append(o.Value)
}
if _, ok := seen[i.Default.Value]; !ok {
rule.errorf(i.Default.Pos, "default value %q of %q input is not included in its options %q", i.Default.Value, n, b.build())
rule.Errorf(i.Default.Pos, "default value %q of %q input is not included in its options %q", i.Default.Value, n, b.build())
}
}
} else {
if len(i.Options) > 0 {
rule.errorf(i.Name.Pos, "\"options\" can not be set to %q input because its input type is not \"choice\"", n)
rule.Errorf(i.Name.Pos, "\"options\" can not be set to %q input because its input type is not \"choice\"", n)
}
if i.Default != nil {
// TODO: Can some check be done for WorkflowDispatchEventInputTypeEnvironment?
// What is suitable for default value of the type? (Or is a default value never suitable?)
switch i.Type {
case WorkflowDispatchEventInputTypeNumber:
if _, err := strconv.ParseFloat(i.Default.Value, 64); err != nil {
rule.errorf(
rule.Errorf(
i.Default.Pos,
"type of %q input is \"number\" but its default value %q cannot be parsed as a float number: %s",
i.Name.Value,
Expand All @@ -269,7 +269,7 @@ func (rule *RuleEvents) checkWorkflowDispatchEvent(event *WorkflowDispatchEvent)
}
case WorkflowDispatchEventInputTypeBoolean:
if d := strings.ToLower(i.Default.Value); d != "true" && d != "false" {
rule.errorf(i.Default.Pos, "type of %q input is \"boolean\". its default value %q must be \"true\" or \"false\"", n, i.Default.Value)
rule.Errorf(i.Default.Pos, "type of %q input is \"boolean\". its default value %q must be \"true\" or \"false\"", n, i.Default.Value)
}
}
}
Expand All @@ -278,7 +278,7 @@ func (rule *RuleEvents) checkWorkflowDispatchEvent(event *WorkflowDispatchEvent)
// Maximum number of inputs is 10
// https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#providing-inputs
if len(event.Inputs) > 10 {
rule.errorf(
rule.Errorf(
event.Pos,
"maximum number of inputs for \"workflow_dispatch\" event is 10 but %d inputs are provided. see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#providing-inputs",
len(event.Inputs),
Expand Down
Loading
Loading