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

Add translation key rule to gotemplate linter #4657

Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c1fd600
Add dummy translation key rule to gotemplatelinter
pkong-ds Aug 26, 2024
2c86098
Fix tree traversal no navigated to nested pipe in template
pkong-ds Aug 27, 2024
22b9906
Detect .Translations.RenderText and $.Translations.RenderText in linter
pkong-ds Aug 27, 2024
a4bdac2
Implement TreeErrorContext
pkong-ds Aug 27, 2024
c6b7751
Implement baseline CheckTranslationKey
pkong-ds Aug 27, 2024
5dad876
Implement CheckTranslationKeyNode
pkong-ds Aug 28, 2024
58d3690
Implement baseline check_translate
pkong-ds Aug 27, 2024
0bc4a33
Implement handleNodeErrFn in translation key rule
pkong-ds Aug 28, 2024
eb4f1cf
Add actual key to lint error
pkong-ds Aug 28, 2024
4b25c42
Add FIXMEs to support TranslationKeyNode being variable or pipe
pkong-ds Aug 28, 2024
a01d020
Implement check translations render text rule
pkong-ds Aug 28, 2024
167d72e
Implement check include rule
pkong-ds Aug 28, 2024
66777ca
Implement check template rule
pkong-ds Aug 28, 2024
0c11bfb
Add key to Rule interface
pkong-ds Aug 28, 2024
78f6082
Allow ignore rule in gotemplatelinter
pkong-ds Aug 28, 2024
583fd67
Ignore translationKey in current linter
pkong-ds Aug 28, 2024
bea25db
Add make lint-translation-key
pkong-ds Aug 28, 2024
31cc33f
Fix include commented out
pkong-ds Aug 29, 2024
29ad54f
Provide less context for translation key pattern error
pkong-ds Aug 29, 2024
af74b15
Remove `please follow ...` in translation key checking
pkong-ds Aug 29, 2024
9d670d8
Reword CheckTranslationKey as CheckTranslationKeyPattern
pkong-ds Aug 29, 2024
548359d
Compile regex on init
pkong-ds Aug 29, 2024
e58e229
Add ^$ to translation key pattern
pkong-ds Aug 29, 2024
7dc420e
Reword finalNewline rule as EOLAtEOF
pkong-ds Aug 29, 2024
1758fef
Rename translationKey rule with kebab case
pkong-ds Aug 29, 2024
11b752c
Reword --ignore-rules to --ignore-rule
pkong-ds Aug 29, 2024
b9b70d2
Make paths argument instead of flags
pkong-ds Aug 29, 2024
d2ad7ac
Fix non-string node print literal node
pkong-ds Aug 29, 2024
4b0aed7
Check if translation key is defined
pkong-ds Aug 29, 2024
f2b3a89
Add err key pattern to linter
pkong-ds Aug 29, 2024
3395045
Allow any [a-z-] as state for translation key
pkong-ds Aug 29, 2024
2da9434
Sort the output
louischan-oursky Aug 29, 2024
a6d2101
Fix incorrect condition
louischan-oursky Aug 29, 2024
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: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ test:
$(MAKE) -C ./k6 go-test
go test ./cmd/... ./pkg/... -timeout 1m30s

.PHONY: lint-translation-keys
lint-translation-keys:
go run ./devtools/gotemplatelinter --ignore-rule indentation --ignore-rule eol-at-eof ./resources/authgear/templates/en/web/authflowv2

.PHONY: lint
lint:
golangci-lint run ./cmd/... ./pkg/... --timeout 7m
Expand All @@ -66,7 +70,7 @@ lint:
-go run ./devtools/importlinter worker api lib util >> .make-lint-expect 2>&1
-go run ./devtools/bandimportlinter ./pkg ./cmd >> .make-lint-expect 2>&1
git diff --exit-code .make-lint-expect > /dev/null 2>&1
go run ./devtools/gotemplatelinter ./resources/authgear/templates/en/web/authflowv2
go run ./devtools/gotemplatelinter --ignore-rule translation-key ./resources/authgear/templates/en/web/authflowv2

.PHONY: fmt
fmt:
Expand Down
39 changes: 39 additions & 0 deletions devtools/gotemplatelinter/args_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"flag"
"strings"
)

type ArgsFlags struct {
Paths []string
RulesToIgnore []string
}

func ParseArgsFlags() ArgsFlags {
var rulesToIgnore RulesToIgnoreFlags
flag.Var(&rulesToIgnore, "ignore-rule", "rules to ignore, repeat this flag to ignore more rules")

flag.Parse()

paths := flag.Args()

return ArgsFlags{
Paths: paths,
RulesToIgnore: rulesToIgnore,
}
}

type RulesToIgnoreFlags []string

func (rs *RulesToIgnoreFlags) String() string {
if rs == nil {
return ""
}
return strings.Join(*rs, ",")
}

func (rs *RulesToIgnoreFlags) Set(value string) error {
*rs = append(*rs, value)
return nil
}
10 changes: 8 additions & 2 deletions devtools/gotemplatelinter/final_newline_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"strings"
)

type FinalNewlineRule struct{}
type EOLAtEOFRule struct{}

func (r FinalNewlineRule) Check(content string, path string) LintViolations {
var _ Rule = EOLAtEOFRule{}

func (r EOLAtEOFRule) Check(content string, path string) LintViolations {
var violations LintViolations
lines := strings.Split(content, "\n")
if len(content) > 0 && !strings.HasSuffix(content, "\n") {
Expand All @@ -21,3 +23,7 @@ func (r FinalNewlineRule) Check(content string, path string) LintViolations {
}
return violations
}

func (r EOLAtEOFRule) Key() string {
return "eol-at-eof"
}
12 changes: 6 additions & 6 deletions devtools/gotemplatelinter/final_newline_rule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import (
. "github.com/smartystreets/goconvey/convey"
)

func TestFinalNewlineRule(t *testing.T) {
func TestEOLAtEOFRule(t *testing.T) {
Convey("Given a empty file", t, func() {
html := ``
violations := FinalNewlineRule{}.Check(html, "test.html")
violations := EOLAtEOFRule{}.Check(html, "test.html")
Convey("Then I expect to see a lint violation", func() {
So(len(violations), ShouldEqual, 0)
})
})

Convey("Given a file with a single line", t, func() {
html := `<!DOCTYPE html><html lang="en"><head><title>Test</title></head><body><p>Test</p></body></html>`
violations := FinalNewlineRule{}.Check(html, "test.html")
violations := EOLAtEOFRule{}.Check(html, "test.html")
Convey("Then I expect to see a lint violation", func() {
So(len(violations), ShouldEqual, 1)
So(violations[0].Path, ShouldEqual, "test.html")
Expand All @@ -30,7 +30,7 @@ func TestFinalNewlineRule(t *testing.T) {
Convey("Given a file with a single line and a newline", t, func() {
html := `<!DOCTYPE html><html lang="en"><head><title>Test</title></head><body><p>Test</p></body></html>
`
violations := FinalNewlineRule{}.Check(html, "test.html")
violations := EOLAtEOFRule{}.Check(html, "test.html")
Convey("Then I expect to see no violation", func() {
So(len(violations), ShouldEqual, 0)
})
Expand All @@ -39,7 +39,7 @@ func TestFinalNewlineRule(t *testing.T) {
Convey("Given a file with a single character and a newline", t, func() {
html := `A
`
violations := FinalNewlineRule{}.Check(html, "test.html")
violations := EOLAtEOFRule{}.Check(html, "test.html")
Convey("Then I expect to see no violation", func() {
So(len(violations), ShouldEqual, 0)
})
Expand All @@ -55,7 +55,7 @@ func TestFinalNewlineRule(t *testing.T) {
<p>Test</p>
</body>
</html>`
violations := FinalNewlineRule{}.Check(html, "test.html")
violations := EOLAtEOFRule{}.Check(html, "test.html")
Convey("Then I expect to see a lint violation", func() {
So(len(violations), ShouldEqual, 1)
So(violations[0].Path, ShouldEqual, "test.html")
Expand Down
6 changes: 6 additions & 0 deletions devtools/gotemplatelinter/indent_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

type IndentationRule struct{}

var _ Rule = IndentationRule{}

func (r IndentationRule) Check(content string, path string) LintViolations {
var violations LintViolations
lines := strings.Split(content, "\n")
Expand All @@ -24,3 +26,7 @@ func (r IndentationRule) Check(content string, path string) LintViolations {
}
return violations
}

func (r IndentationRule) Key() string {
return "indentation"
}
63 changes: 49 additions & 14 deletions devtools/gotemplatelinter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import (
"os"
"path/filepath"
"strings"

"github.com/authgear/authgear-server/pkg/util/slice"
)

type Rule interface {
Check(content string, path string) LintViolations
Key() string
}

type LintViolation struct {
Expand Down Expand Up @@ -98,25 +101,57 @@ func (l *Linter) LintFile(path string, info os.FileInfo) (violations LintViolati
return
}

func constructRules(rulesToIgnore []string) []Rule {
indentationRule := IndentationRule{}
EOLAtEOFRule := EOLAtEOFRule{}
translationKeyRule := TranslationKeyRule{}
rules := []Rule{
indentationRule,
EOLAtEOFRule,
translationKeyRule,
}
ignoreRuleFn := func(rule Rule) {
rules = slice.Filter[Rule](rules, func(r Rule) bool {
return r != rule
})
}
for _, ruleToIgnore := range rulesToIgnore {
switch ruleToIgnore {
case indentationRule.Key():
ignoreRuleFn(indentationRule)
case EOLAtEOFRule.Key():
ignoreRuleFn(EOLAtEOFRule)
case translationKeyRule.Key():
ignoreRuleFn(translationKeyRule)
}
}

return rules
}

func doMain() (violations LintViolations, err error) {
if len(os.Args) < 2 {
err = fmt.Errorf("usage: gotemplatelinter <path/to/htmls>")
err = fmt.Errorf("usage: gotemplatelinter --path <path/to/htmls> --ignore-rule rule1ToIgnore --ignore-rule rule2ToIgnore")
return
}
path := os.Args[1]
linter := Linter{
IgnorePatterns: []string{
"__generated_asset.html",
},
Rules: []Rule{
IndentationRule{},
FinalNewlineRule{},
},
Path: path,
argsFlags := ParseArgsFlags()
rules := constructRules(argsFlags.RulesToIgnore)
ignorePatterns := []string{
"__generated_asset.html",
}
violations, err = linter.Lint()
if err != nil {
return
linters := slice.Map(argsFlags.Paths, func(path string) Linter {
return Linter{
IgnorePatterns: ignorePatterns,
Rules: rules,
Path: path,
}
})
for _, linter := range linters {
newViolations, err := linter.Lint()
if err != nil {
return violations, err
}
violations = append(violations, newViolations...)
}

return
Expand Down
37 changes: 37 additions & 0 deletions devtools/gotemplatelinter/transation_key_rule_check_include.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"strings"
"text/template/parse"
)

// validate `include` command
//
// e.g. (include "v2-error-screen-title" nil)
// e.g. (include (printf "v2-oauth-branding-%s" .provider_type) nil)
// e.g. (include $description_key nil)
func CheckCommandInclude(includeNode *parse.CommandNode) (err error) {
// 2nd arg should be translation key
for idx, arg := range includeNode.Args {
if idx == 1 {
if isHTMLInclude(arg) { // false positive
break
}
err = CheckTranslationKeyNode(arg)
if err != nil {
return err
}
}

}
return
}

func isHTMLInclude(arg parse.Node) bool {
if str, ok := arg.(*parse.StringNode); ok {
if strings.Contains(str.Text, ".html") {
return true
}
}
return false
}
27 changes: 27 additions & 0 deletions devtools/gotemplatelinter/transation_key_rule_check_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"strings"
"text/template/parse"
)

// validate `{{ template }}` nodes
//
// e.g. {{template "setup-totp-get-google-authenticator-description"}}
// e.g. {{template "setup-totp-raw-secret" (dict "secret" $.Secret)}}
// e.g. {{template "settings-totp-item-description" (dict "time" .CreatedAt "rfc3339" (rfc3339 .CreatedAt))}}
func CheckTemplate(templateNode *parse.TemplateNode) (err error) {
if isHTMLTemplate(templateNode) { // false positive
return
}
translationKey := templateNode.Name
err = CheckTranslationKeyPattern(translationKey)
if err != nil {
return err
}
return
}

func isHTMLTemplate(templateNode *parse.TemplateNode) bool {
return strings.Contains(templateNode.Name, ".html")
}
22 changes: 22 additions & 0 deletions devtools/gotemplatelinter/transation_key_rule_check_translate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"text/template/parse"
)

// validate `translate` command
//
// e.g. (translate "app.name" nil)
func CheckCommandTranslate(translateNode *parse.CommandNode) (err error) {
// 2nd arg should be translation key
for idx, arg := range translateNode.Args {
if idx == 1 {
err = CheckTranslationKeyNode(arg)
if err != nil {
return err
}
}

}
return
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"text/template/parse"
)

// validate commands with variable `$.Translations.RenderText` or field `.Translations.RenderText`
//
// example: `($.Translations.RenderText "customer-support-link" nil)`
// example: (.Translations.RenderText "terms-of-service-link" nil)
func CheckCommandTranslationsRenderText(node *parse.CommandNode) (err error) {
// 2nd arg should be translation key
for idx, arg := range node.Args {
if idx == 1 {
err = CheckTranslationKeyNode(arg)
if err != nil {
return err
}
}

}
return
}
Loading
Loading