Skip to content

Commit 44b0c79

Browse files
abgoyaldeadprogram
authored andcommitted
loader: support "all:" prefix in //go:embed patterns
The "all:" prefix in //go:embed directives instructs the compiler to include hidden files (starting with "." or "_") when embedding a directory. Previously, the prefix was passed literally to path.Match, which would never match. Introduce an embedPattern struct that parses and validates the pattern at construction time via newEmbedPattern(). The "all:" prefix is stripped once and stored along with the glob pattern, avoiding repeated string prefix checks. Pattern validation is absorbed into the constructor, guaranteeing that any embedPattern value is valid and eliminating the need for separate validation loops. Fixes embedding with patterns like "//go:embed all:static".
1 parent 27e5bae commit 44b0c79

File tree

3 files changed

+71
-45
lines changed

3 files changed

+71
-45
lines changed

loader/loader.go

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ func (p *Package) extractEmbedLines(addError func(error)) {
549549
}
550550

551551
// Look for //go:embed comments.
552-
var allPatterns []string
552+
var allPatterns []embedPattern
553553
for _, comment := range doc.List {
554554
if comment.Text != "//go:embed" && !strings.HasPrefix(comment.Text, "//go:embed ") {
555555
continue
@@ -577,21 +577,7 @@ func (p *Package) extractEmbedLines(addError func(error)) {
577577
})
578578
continue
579579
}
580-
for _, pattern := range patterns {
581-
// Check that the pattern is well-formed.
582-
// It must be valid: the Go toolchain has already
583-
// checked for invalid patterns. But let's check
584-
// anyway to be sure.
585-
if _, err := path.Match(pattern, ""); err != nil {
586-
addError(types.Error{
587-
Fset: p.program.fset,
588-
Pos: comment.Pos(),
589-
Msg: "invalid pattern syntax",
590-
})
591-
continue
592-
}
593-
allPatterns = append(allPatterns, pattern)
594-
}
580+
allPatterns = append(allPatterns, patterns...)
595581
}
596582

597583
if len(allPatterns) != 0 {
@@ -624,8 +610,8 @@ func (p *Package) extractEmbedLines(addError func(error)) {
624610
// Match all //go:embed patterns against the embed files
625611
// provided by `go list`.
626612
for _, name := range p.EmbedFiles {
627-
for _, pattern := range allPatterns {
628-
if matchPattern(pattern, name) {
613+
for _, ep := range allPatterns {
614+
if ep.Match(name) {
629615
p.EmbedGlobals[globalName] = append(p.EmbedGlobals[globalName], &EmbedFile{
630616
Name: name,
631617
NeedsData: byteSlice,
@@ -641,13 +627,37 @@ func (p *Package) extractEmbedLines(addError func(error)) {
641627
}
642628
}
643629

644-
// matchPattern returns true if (and only if) the given pattern would match the
645-
// filename. The pattern could also match a parent directory of name, in which
646-
// case hidden files do not match.
647-
func matchPattern(pattern, name string) bool {
630+
// embedPattern represents a valid //go:embed pattern.
631+
// See: https://pkg.go.dev/embed#hdr-Directives
632+
type embedPattern struct {
633+
pattern string // glob pattern without "all:" prefix
634+
includeHidden bool // true if "all:" prefix was present
635+
}
636+
637+
// newEmbedPattern parses and validates a //go:embed pattern.
638+
// The "all:" prefix (if present) is stripped and reflected in includeHidden.
639+
// Returns an error if the pattern syntax is invalid.
640+
func newEmbedPattern(s string) (embedPattern, error) {
641+
ep := embedPattern{pattern: s}
642+
if strings.HasPrefix(s, "all:") {
643+
ep.pattern = s[4:]
644+
ep.includeHidden = true
645+
}
646+
if _, err := path.Match(ep.pattern, ""); err != nil {
647+
return embedPattern{}, err
648+
}
649+
return ep, nil
650+
}
651+
652+
// Match returns true if (and only if) the pattern matches the given filename.
653+
// The pattern could also match a parent directory of name, in which case hidden
654+
// files do not match (unless includeHidden is true).
655+
func (ep embedPattern) Match(name string) bool {
656+
pattern := ep.pattern
657+
includeHidden := ep.includeHidden
658+
648659
// Match this file.
649-
matched, _ := path.Match(pattern, name)
650-
if matched {
660+
if matched, _ := path.Match(pattern, name); matched {
651661
return true
652662
}
653663

@@ -661,17 +671,20 @@ func matchPattern(pattern, name string) bool {
661671
dir = path.Clean(dir)
662672
if matched, _ := path.Match(pattern, dir); matched {
663673
// Pattern matches the directory.
664-
suffix := name[len(dir):]
665-
if strings.Contains(suffix, "/_") || strings.Contains(suffix, "/.") {
666-
// Pattern matches a hidden file.
667-
// Hidden files are included when listed directly as a
668-
// pattern, but not when they are part of a directory tree.
669-
// Source:
670-
// > If a pattern names a directory, all files in the
671-
// > subtree rooted at that directory are embedded
672-
// > (recursively), except that files with names beginning
673-
// > with ‘.’ or ‘_’ are excluded.
674-
return false
674+
if !includeHidden {
675+
suffix := name[len(dir):]
676+
if strings.Contains(suffix, "/_") || strings.Contains(suffix, "/.") {
677+
// Pattern matches a hidden file.
678+
// Hidden files are included when listed directly as a
679+
// pattern, or when the "all:" prefix is used, but not
680+
// when they are part of a directory tree without "all:".
681+
// Source:
682+
// > If a pattern names a directory, all files in the
683+
// > subtree rooted at that directory are embedded
684+
// > (recursively), except that files with names beginning
685+
// > with ‘.’ or ‘_’ are excluded.
686+
return false
687+
}
675688
}
676689
return true
677690
}
@@ -680,11 +693,12 @@ func matchPattern(pattern, name string) bool {
680693

681694
// parseGoEmbed is like strings.Fields but for a //go:embed line. It parses
682695
// regular fields and quoted fields (that may contain spaces).
683-
func (p *Package) parseGoEmbed(args string, pos token.Pos) (patterns []string, err error) {
696+
func (p *Package) parseGoEmbed(args string, pos token.Pos) (patterns []embedPattern, err error) {
684697
args = strings.TrimSpace(args)
685698
initialLen := len(args)
686699
for args != "" {
687700
patternPos := pos + token.Pos(initialLen-len(args))
701+
var pattern string
688702
switch args[0] {
689703
case '`', '"', '\\':
690704
// Parse the next pattern using the Go scanner.
@@ -703,8 +717,7 @@ func (p *Package) parseGoEmbed(args string, pos token.Pos) (patterns []string, e
703717
Msg: "invalid quoted string in //go:embed",
704718
}
705719
}
706-
pattern := constant.StringVal(constant.MakeFromLiteral(lit, tok, 0))
707-
patterns = append(patterns, pattern)
720+
pattern = constant.StringVal(constant.MakeFromLiteral(lit, tok, 0))
708721
args = strings.TrimLeftFunc(args[len(lit):], unicode.IsSpace)
709722
default:
710723
// The value is just a regular value.
@@ -713,17 +726,18 @@ func (p *Package) parseGoEmbed(args string, pos token.Pos) (patterns []string, e
713726
if index < 0 {
714727
index = len(args)
715728
}
716-
pattern := args[:index]
717-
patterns = append(patterns, pattern)
729+
pattern = args[:index]
718730
args = strings.TrimLeftFunc(args[len(pattern):], unicode.IsSpace)
719731
}
720-
if _, err := path.Match(patterns[len(patterns)-1], ""); err != nil {
732+
ep, err := newEmbedPattern(pattern)
733+
if err != nil {
721734
return nil, types.Error{
722735
Fset: p.program.fset,
723736
Pos: patternPos,
724737
Msg: "invalid pattern syntax",
725738
}
726739
}
740+
patterns = append(patterns, ep)
727741
}
728742
return patterns, nil
729743
}

testdata/embed/embed.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,24 @@ var (
2020
//go:embed a/b/.hidden
2121
var hidden string
2222

23+
// A test to check that hidden files ARE included when using "all:" prefix.
24+
//go:embed all:a
25+
var allFiles embed.FS
26+
2327
var helloStringBytes = []byte(helloString)
2428

2529
func main() {
2630
println("string:", strings.TrimSpace(helloString))
2731
println("bytes:", strings.TrimSpace(string(helloBytes)))
2832
println("[]byte(string):", strings.TrimSpace(string(helloStringBytes)))
2933
println("files:")
30-
readFiles(".")
34+
readFiles(".", files)
35+
println("all:a files (should include .hidden):")
36+
readFiles(".", allFiles)
3137
}
3238

33-
func readFiles(dir string) {
34-
entries, err := files.ReadDir(dir)
39+
func readFiles(dir string, fs embed.FS) {
40+
entries, err := fs.ReadDir(dir)
3541
if err != nil {
3642
println(err.Error())
3743
return
@@ -43,7 +49,7 @@ func readFiles(dir string) {
4349
}
4450
println("-", entryPath)
4551
if entry.IsDir() {
46-
readFiles(entryPath)
52+
readFiles(entryPath, fs)
4753
}
4854
}
4955
}

testdata/embed/out.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@ files:
77
- a/b/bar.txt
88
- a/b/foo.txt
99
- hello.txt
10+
all:a files (should include .hidden):
11+
- a
12+
- a/b
13+
- a/b/.hidden
14+
- a/b/bar.txt
15+
- a/b/foo.txt

0 commit comments

Comments
 (0)