Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5dd436c

Browse files
authored
feat(examples): add linting to all examples (#12595)
Fixes#12588
1 parent410a7d5 commit5dd436c

File tree

3 files changed

+157
-76
lines changed

3 files changed

+157
-76
lines changed

‎Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ else
428428
endif
429429
.PHONY: fmt/shfmt
430430

431-
lint: lint/shellcheck lint/go lint/ts lint/helm lint/site-icons
431+
lint: lint/shellcheck lint/go lint/ts lint/examples lint/helm lint/site-icons
432432
.PHONY: lint
433433

434434
lint/site-icons:
@@ -447,6 +447,10 @@ lint/go:
447447
golangci-lint run
448448
.PHONY: lint/go
449449

450+
lint/examples:
451+
go run ./scripts/examplegen/main.go -lint
452+
.PHONY: lint/examples
453+
450454
# Use shfmt to determine the shell files, takes editorconfig into consideration.
451455
lint/shellcheck:$(SHELL_SRC_FILES)
452456
echo"--- shellcheck"

‎examples/templates/incus/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
display_name:Incus System Container with Docker
33
description:Develop in an Incus System Container with Docker using incus
4-
icon:/icon/lxc.svg
4+
icon:../../../site/static/icon/lxc.svg
55
maintainer_github:coder
66
verified:true
77
tags:[local, incus, lxc, lxd]

‎scripts/examplegen/main.go

Lines changed: 151 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package main
33
import (
44
"bytes"
55
"encoding/json"
6+
"errors"
7+
"flag"
68
"fmt"
79
"go/parser"
810
"go/token"
11+
"io"
912
"io/fs"
1013
"os"
1114
"path"
@@ -24,122 +27,196 @@ const (
2427
)
2528

2629
funcmain() {
27-
iferr:=run();err!=nil {
28-
panic(err)
30+
lint:=flag.Bool("lint",false,"Lint **all** the examples instead of generating the examples.gen.json file")
31+
flag.Parse()
32+
33+
iferr:=run(*lint);err!=nil {
34+
_,_=fmt.Fprintf(os.Stderr,"error: %+v\n",err)
35+
os.Exit(1)
2936
}
3037
}
3138

32-
funcrun()error {
39+
//nolint:revive // This is a script, not a library.
40+
funcrun(lintbool)error {
3341
fset:=token.NewFileSet()
3442
src,err:=parser.ParseFile(fset,filepath.Join(examplesDir,examplesSrc),nil,parser.ParseComments)
3543
iferr!=nil {
3644
returnerr
3745
}
3846

47+
projectFS:=os.DirFS(".")
48+
examplesFS:=os.DirFS(examplesDir)
49+
3950
varpaths []string
40-
for_,comment:=rangesrc.Comments {
41-
for_,line:=rangecomment.List {
42-
ifs,ok:=parseEmbedTag(line.Text);ok&&!strings.HasSuffix(s,".json") {
43-
paths=append(paths,s)
51+
iflint {
52+
files,err:=fs.ReadDir(examplesFS,"templates")
53+
iferr!=nil {
54+
returnerr
55+
}
56+
57+
for_,f:=rangefiles {
58+
if!f.IsDir() {
59+
continue
60+
}
61+
paths=append(paths,filepath.Join("templates",f.Name()))
62+
}
63+
}else {
64+
for_,comment:=rangesrc.Comments {
65+
for_,line:=rangecomment.List {
66+
ifs,ok:=parseEmbedTag(line.Text);ok&&!strings.HasSuffix(s,".json") {
67+
paths=append(paths,s)
68+
}
4469
}
4570
}
4671
}
4772

4873
varexamples []codersdk.TemplateExample
49-
files:=os.DirFS(examplesDir)
74+
varerrs []error
5075
for_,name:=rangepaths {
51-
dir,err:=fs.Stat(files,name)
76+
te,err:=parseTemplateExample(projectFS,examplesFS,name)
5277
iferr!=nil {
53-
returnerr
54-
}
55-
if!dir.IsDir() {
78+
errs=append(errs,err)
5679
continue
5780
}
58-
exampleID:=dir.Name()
59-
// Each one of these is a example!
60-
readme,err:=fs.ReadFile(files,path.Join(name,"README.md"))
61-
iferr!=nil {
62-
returnxerrors.Errorf("example %q does not contain README.md",exampleID)
81+
ifte!=nil {
82+
examples=append(examples,*te)
6383
}
84+
}
6485

65-
frontMatter,err:=pageparser.ParseFrontMatterAndContent(bytes.NewReader(readme))
86+
iflen(errs)>0 {
87+
returnxerrors.Errorf("parse failed: %w",errors.Join(errs...))
88+
}
89+
90+
varw io.Writer=os.Stdout
91+
iflint {
92+
w=io.Discard
93+
}
94+
95+
_,err=fmt.Fprint(w,"// Code generated by examplegen. DO NOT EDIT.\n")
96+
iferr!=nil {
97+
returnerr
98+
}
99+
100+
enc:=json.NewEncoder(w)
101+
enc.SetIndent(""," ")
102+
returnenc.Encode(examples)
103+
}
104+
105+
funcparseTemplateExample(projectFS,examplesFS fs.FS,namestring) (te*codersdk.TemplateExample,errerror) {
106+
varerrs []error
107+
deferfunc() {
66108
iferr!=nil {
67-
returnxerrors.Errorf("parse example %q front matter: %w",exampleID,err)
109+
errs=append([]error{err},errs...)
68110
}
69-
70-
nameRaw,exists:=frontMatter.FrontMatter["display_name"]
71-
if!exists {
72-
returnxerrors.Errorf("example %q front matter does not contain name",exampleID)
111+
iflen(errs)>0 {
112+
err=xerrors.Errorf("example %q has errors",name)
113+
for_,e:=rangeerrs {
114+
err=errors.Join(err,e)
115+
}
73116
}
117+
}()
74118

75-
name,valid:=nameRaw.(string)
76-
if!valid {
77-
returnxerrors.Errorf("example %q name isn't a string",exampleID)
78-
}
119+
dir,err:=fs.Stat(examplesFS,name)
120+
iferr!=nil {
121+
returnnil,err
122+
}
123+
if!dir.IsDir() {
124+
//nolint:nilnil // This is a script, not a library.
125+
returnnil,nil
126+
}
79127

80-
descriptionRaw,exists:=frontMatter.FrontMatter["description"]
81-
if!exists {
82-
returnxerrors.Errorf("example %q front matter does not contain name",exampleID)
83-
}
128+
exampleID:=dir.Name()
129+
// Each one of these is a example!
130+
readme,err:=fs.ReadFile(examplesFS,path.Join(name,"README.md"))
131+
iferr!=nil {
132+
returnnil,xerrors.New("missing README.md")
133+
}
84134

85-
description,valid:=descriptionRaw.(string)
86-
if!valid {
87-
returnxerrors.Errorf("example %q description isn't a string",exampleID)
88-
}
135+
frontMatter,err:=pageparser.ParseFrontMatterAndContent(bytes.NewReader(readme))
136+
iferr!=nil {
137+
returnnil,xerrors.Errorf("parse front matter: %w",err)
138+
}
89139

90-
tags:= []string{}
91-
tagsRaw,exists:=frontMatter.FrontMatter["tags"]
92-
ifexists {
93-
tagsI,valid:=tagsRaw.([]interface{})
94-
if!valid {
95-
returnxerrors.Errorf("example %q tags isn't a slice: type %T",exampleID,tagsRaw)
96-
}
140+
// Make sure validation here is in sync with requirements for
141+
// coder/registry.
142+
displayName,err:=getString(frontMatter.FrontMatter,"display_name")
143+
iferr!=nil {
144+
errs=append(errs,err)
145+
}
146+
147+
description,err:=getString(frontMatter.FrontMatter,"description")
148+
iferr!=nil {
149+
errs=append(errs,err)
150+
}
151+
152+
_,err=getString(frontMatter.FrontMatter,"maintainer_github")
153+
iferr!=nil {
154+
errs=append(errs,err)
155+
}
156+
157+
tags:= []string{}
158+
tagsRaw,exists:=frontMatter.FrontMatter["tags"]
159+
ifexists {
160+
tagsI,valid:=tagsRaw.([]interface{})
161+
if!valid {
162+
errs=append(errs,xerrors.Errorf("tags isn't a slice: type %T",tagsRaw))
163+
}else {
97164
for_,tagI:=rangetagsI {
98165
tag,valid:=tagI.(string)
99166
if!valid {
100-
returnxerrors.Errorf("example %q tag isn't a string: type %T",exampleID,tagI)
167+
errs=append(errs,xerrors.Errorf("tag isn't a string: type %T",tagI))
168+
continue
101169
}
102170
tags=append(tags,tag)
103171
}
104172
}
173+
}
105174

106-
variconstring
107-
iconRaw,exists:=frontMatter.FrontMatter["icon"]
108-
ifexists {
109-
icon,valid=iconRaw.(string)
110-
if!valid {
111-
returnxerrors.Errorf("example %q icon isn't a string",exampleID)
112-
}
113-
icon,err=filepath.Rel("../site/static/",filepath.Join(examplesDir,name,icon))
114-
iferr!=nil {
115-
returnxerrors.Errorf("example %q icon is not in site/static: %w",exampleID,err)
116-
}
117-
// The FE needs a static path!
118-
icon="/"+icon
175+
variconstring
176+
icon,err=getString(frontMatter.FrontMatter,"icon")
177+
iferr!=nil {
178+
errs=append(errs,err)
179+
}else {
180+
cleanPath:=filepath.Clean(filepath.Join(examplesDir,name,icon))
181+
_,err:=fs.Stat(projectFS,cleanPath)
182+
iferr!=nil {
183+
errs=append(errs,xerrors.Errorf("icon does not exist: %w",err))
119184
}
185+
if!strings.HasPrefix(cleanPath,filepath.Join("site","static")) {
186+
errs=append(errs,xerrors.Errorf("icon is not in site/static/: %q",icon))
187+
}
188+
icon,err=filepath.Rel(filepath.Join("site","static"),cleanPath)
189+
iferr!=nil {
190+
errs=append(errs,xerrors.Errorf("cannot make icon relative to site/static: %w",err))
191+
}
192+
}
120193

121-
examples=append(examples, codersdk.TemplateExample{
122-
ID:exampleID,
123-
Name:name,
124-
Description:description,
125-
Icon:icon,
126-
Tags:tags,
127-
Markdown:string(frontMatter.Content),
128-
129-
// URL is set by examples/examples.go.
130-
})
194+
iflen(errs)>0 {
195+
returnnil,xerrors.New("front matter validation failed")
131196
}
132197

133-
w:=os.Stdout
198+
return&codersdk.TemplateExample{
199+
ID:exampleID,
200+
Name:displayName,
201+
Description:description,
202+
Icon:"/"+icon,// The FE needs a static path!
203+
Tags:tags,
204+
Markdown:string(frontMatter.Content),
134205

135-
_,err=fmt.Fprint(w,"// Code generated by examplegen. DO NOT EDIT.\n")
136-
iferr!=nil {
137-
returnerr
138-
}
206+
// URL is set by examples/examples.go.
207+
},nil
208+
}
139209

140-
enc:=json.NewEncoder(os.Stdout)
141-
enc.SetIndent(""," ")
142-
returnenc.Encode(examples)
210+
funcgetString(mmap[string]any,keystring) (string,error) {
211+
v,ok:=m[key]
212+
if!ok {
213+
return"",xerrors.Errorf("front matter does not contain %q",key)
214+
}
215+
vv,ok:=v.(string)
216+
if!ok {
217+
return"",xerrors.Errorf("%q isn't a string",key)
218+
}
219+
returnvv,nil
143220
}
144221

145222
funcparseEmbedTag(sstring) (string,bool) {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp