Skip to content
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
26 changes: 26 additions & 0 deletions internal/graphql/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,21 @@ const (
RuleNoUnreachableTypes = "no-unreachable-types"
)

// These constants name the 2 diagnostics raised about "devbase-lint-"
// suppression comments. Unlike the Tier 2 and Tier 3 rules, they run
// without being enabled, at "error" severity; scripts/devbase.yaml can
// lower either to "warn" or turn it "off". They are never themselves
// suppressible.
const (
// RuleInvalidSuppression reports a suppression comment that is
// malformed, names no rule or an unknown one, or has no reason.
RuleInvalidSuppression = "invalid-suppression"

// RuleUnusedSuppression reports a suppression comment that
// suppressed no violation of a rule it names.
RuleUnusedSuppression = "unused-suppression"
)

// Rule is the per-rule override for a single lint rule. It accepts
// two YAML shapes:
//
Expand Down Expand Up @@ -316,6 +331,17 @@ func (c *Lint) MergeExcludes(extra ...string) []string {
return merged
}

// Disabled reports whether scripts/devbase.yaml sets the rule to "off".
// A rule absent from the config is not disabled, so diagnostics that run
// by default stay on. c may be nil.
func (c *Lint) Disabled(rule string) bool {
if c == nil {
return false
}
rc, ok := c.Rules[rule]
return ok && rc.Severity == SeverityOff
}

// Enabled reports whether the rule should run at all. A Tier 2 or Tier 3
// rule is enabled only once scripts/devbase.yaml gives it a severity
// other than SeverityOff -- it does not run by default, matching
Expand Down
27 changes: 27 additions & 0 deletions internal/graphql/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,33 @@ func TestEnabledRequiresAnExplicitNonOffSeverity(t *testing.T) {
}
}

func TestDisabledIsFalseByDefault(t *testing.T) {
var nilConfig *Lint
assert.Equal(t, nilConfig.Disabled(RuleUnusedSuppression), false)

c := &Lint{}
assert.Equal(t, c.Disabled(RuleUnusedSuppression), false)
}

func TestDisabledRequiresAnExplicitOffSeverity(t *testing.T) {
cases := []struct {
name string
severity Severity
want bool
}{
{name: "off", severity: SeverityOff, want: true},
{name: "warn", severity: SeverityWarn, want: false},
{name: "error", severity: SeverityError, want: false},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
c := &Lint{Rules: map[string]Rule{RuleUnusedSuppression: {Severity: tc.severity}}}
assert.Equal(t, c.Disabled(RuleUnusedSuppression), tc.want)
})
}
}

func TestSeverityOfDefaultsToErrorWhenRuleIsNotInTheMap(t *testing.T) {
var nilConfig *Lint
assert.Equal(t, nilConfig.SeverityOf("unique-type-names"), SeverityError)
Expand Down
9 changes: 6 additions & 3 deletions internal/graphql/lint/eslint_directives.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,14 @@ func suppresses(directives []eslintDirective, rule string, line int) bool {
}

// unsuppressibleRules are the rules a directive comment never silences.
// Tier 1 violations currently return from FilesFromSources before
// suppression runs, so this guard only protects against that changing.
// Tier 1 violations return from FilesFromSources before suppression runs,
// so for those this guard only protects against that changing. The two
// suppression diagnostics are added before the "eslint-" directives are
// applied, so this set is what keeps them visible.
//
//nolint:gochecknoglobals // Why: built once, never mutated.
var unsuppressibleRules = set.Of(append(config.Tier1RuleNames(), UnclassifiedRule)...)
var unsuppressibleRules = set.Of(append(config.Tier1RuleNames(), UnclassifiedRule,
config.RuleInvalidSuppression, config.RuleUnusedSuppression)...)

// withoutSuppressed returns violations minus every Tier 2 or Tier 3
// violation silenced by a directive comment in the source it was found
Expand Down
14 changes: 8 additions & 6 deletions internal/graphql/lint/hashtag.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@ func trailingCommentStarts(src *ast.Source) (map[int]bool, error) {
return trailing, nil
}

// isEslintDirective reports whether c is an ESLint directive comment,
// such as "# eslint-disable ...". @graphql-eslint never treats these as
// descriptions.
func isEslintDirective(c *ast.Comment) bool {
return strings.HasPrefix(strings.TrimSpace(c.Text()), "eslint")
// isDirectiveComment reports whether c is a directive comment, such as
// "# eslint-disable ..." or "# devbase-lint-disable-next-line ...". A
// directive is never a description: @graphql-eslint skips the ESLint ones,
// and the same holds for devbase's own.
func isDirectiveComment(c *ast.Comment) bool {
text := strings.TrimSpace(c.Text())
return strings.HasPrefix(text, "eslint") || strings.HasPrefix(text, devbaseDirectivePrefix)
}

// noHashtagDescriptionViolations reports a RuleNoHashtagDescription
Expand All @@ -103,7 +105,7 @@ func noHashtagDescriptionViolations(sites []descriptionSite, trailing map[int]bo
// Only the last comment of a group is reported, so only it is
// checked for being a directive.
last := group.List[len(group.List)-1]
if isEslintDirective(last) {
if isDirectiveComment(last) {
continue
}
if len(group.List) == 1 && trailing[last.Position.Start] {
Expand Down
4 changes: 4 additions & 0 deletions internal/graphql/lint/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ func FilesFromSources(fileSources []*ast.Source, cfg *config.Lint) ([]Violation,
if err != nil {
return nil, err
}
violations, err = withNativeSuppression(fileSources, violations, cfg)
if err != nil {
return nil, err
}

return withoutSuppressed(fileSources, violations)
}
Expand Down
Loading
Loading