-
Notifications
You must be signed in to change notification settings - Fork 5
/
file.go
164 lines (138 loc) · 4.28 KB
/
file.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package gen
import (
"bytes"
"fmt"
"go/format"
"strings"
"github.com/ydnar/wasm-tools-go/internal/codec"
)
// File represents a generated file. It may be a Go file
type File struct {
// Name is the short name of the file.
// If Name ends in ".go" this file will be treated as a Go file.
Name string
// GeneratedBy is the name of the program that generated this file.
// Leave empty to omit the "Code generated by ..." header.
GeneratedBy string
// Build contains build tags, serialized as //go:build ...
// Ignored if this is not a Go file.
Build string
// PackageDocs are doc comments that preceed the package declaration.
// These will be wrapped and each line prefixed with // when serialized.
// Ignored if this is not a Go file.
PackageDocs string
// Package this file belongs to.
Package *Package
// Scope is the naming scope of this file, used for package import names.
Scope
// Imports maps Go package imports from package path to local name, e.g. {"encoding/json": "json"}.
Imports map[string]string
// Content is the file contents.
Content []byte
}
// NewFile returns a newly initialized file.
func NewFile(pkg *Package, name string) *File {
return &File{
Name: name,
Package: pkg,
Scope: NewScope(pkg),
Imports: make(map[string]string),
}
}
// IsGo returns true if f represents a Go file.
func (f *File) IsGo() bool {
return strings.HasSuffix(f.Name, ".go")
}
// Write implements io.Writer.
func (f *File) Write(content []byte) (int, error) {
f.Content = append(f.Content, content...)
return len(content), nil
}
const HeaderPattern = `// Code generated by %s. DO NOT EDIT.`
// Bytes returns the byte values of this file.
func (f *File) Bytes() ([]byte, error) {
if !f.IsGo() {
return f.Content, nil
}
var b bytes.Buffer
if f.GeneratedBy != "" {
b.WriteString(fmt.Sprintf(HeaderPattern, f.GeneratedBy))
b.WriteString("\n\n")
}
if f.Build != "" {
b.WriteString("//go:build ")
b.WriteString(f.Build)
b.WriteString("\n\n")
}
if f.PackageDocs != "" {
b.WriteString(FormatDocComments(f.PackageDocs, false))
}
b.WriteString("package ")
b.WriteString(f.Package.Name)
b.WriteString("\n\n")
if len(f.Imports) > 0 {
b.Write(Imports(f.Imports))
b.WriteString("\n\n")
}
b.Write(f.Content)
unformatted := b.Bytes()
formatted, err := format.Source(unformatted)
if err != nil {
return unformatted, fmt.Errorf("error in %s: %w", f.Name, err)
}
return formatted, nil
}
// DeclareName adds a package-scoped identifier to [File] f.
// It additionally checks the file-scoped declarations (local package names).
// It returns the package-unique name (which may be different than name).
func (f *File) DeclareName(name string) string {
return f.Package.DeclareName(f.Scope.DeclareName(name))
}
// Import imports the Go package specified by path, returning the local name for the imported package.
// The path argument may have an optional "#name" suffix to specify the local name.
// The returned local name may differ from the specified local name.
func (f *File) Import(path string) string {
path, name := ParseSelector(path)
if path == f.Package.Path {
// Can't import self
return ""
}
if f.Imports[path] == "" {
f.Imports[path] = f.Scope.DeclareName(name)
}
return f.Imports[path]
}
// RelativeName returns a file and package-relative string for a [Package] and name.
// If f belongs to pkg, it returns the local name.
// If f belongs to a different package, it first imports the package,
// then returns a name prefixed with the imported package name.
func (f *File) RelativeName(pkg *Package, name string) string {
// FIXME: is this redundant, but safer?
if pkg == f.Package || pkg.Path == f.Package.Path {
return name
}
pkgName := f.Import(pkg.Path + "#" + pkg.Name)
return pkgName + "." + name
}
// Imports returns Go import syntax for imports.
// The imports argument is a map of import path to local name.
func Imports(imports map[string]string) []byte {
if len(imports) == 0 {
return nil
}
var b bytes.Buffer
b.WriteString("import (\n")
for _, path := range codec.SortedKeys(imports) {
name := imports[path]
b.WriteRune('\t')
if path != name && !strings.HasSuffix(path, "/"+name) {
b.WriteString(name)
b.WriteRune(' ')
}
b.WriteString("\"")
b.WriteString(path)
b.WriteString("\"\n")
}
b.WriteString(")")
return b.Bytes()
}