Skip to content

Commit 6ec2983

Browse files
authored
Merge pull request #1 from arduino/development
Create tool for linting Arduino projects
2 parents 3a2722e + 09bbd6a commit 6ec2983

32 files changed

+2006
-0
lines changed

Diff for: README.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# arduino-check
2+
3+
`arduino-check` automatically checks for common problems in your [Arduino](https://www.arduino.cc/) projects:
4+
5+
- Sketches
6+
- Libraries

Diff for: arduino-library-properties-schema.json

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$id": "http://github.com/arduino/arduino-check/arduino-library-properties-schema.json",
4+
"title": "Arduino library.properties JSON schema",
5+
"description": "library.properties is the metadata file for Arduino libraries. See: https://arduino.github.io/arduino-cli/latest/library-specification/#library-metadata",
6+
"$comment": "For information on the Arduino library.properties format, see https://godoc.org/github.com/arduino/go-properties-orderedmap",
7+
"type": "object",
8+
"properties": {
9+
"name": {
10+
"type": "string",
11+
"minLength": 1,
12+
"maxLength": 63,
13+
"pattern": "^(([a-zA-Z][a-zA-Z0-9 _\\.\\-]*)|([0-9][a-zA-Z0-9 _\\.\\-]*[a-zA-Z][a-zA-Z0-9 _\\.\\-]*))$"
14+
},
15+
"version": {
16+
"type": "string",
17+
"$comment": "https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string adjusted to also allow MAJOR.MINOR and with unused non-capturing group syntax removed",
18+
"pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(\\.(0|[1-9]\\d*))?(-((0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\\+([0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*))?$"
19+
},
20+
"author": {
21+
"type": "string",
22+
"minLength": 1
23+
},
24+
"maintainer": {
25+
"type": "string",
26+
"minLength": 1
27+
},
28+
"sentence": {
29+
"type": "string",
30+
"minLength": 1
31+
},
32+
"paragraph": {
33+
"type": "string"
34+
},
35+
"category": {
36+
"type": "string",
37+
"enum": [
38+
"Display",
39+
"Communication",
40+
"Signal Input/Output",
41+
"Sensors",
42+
"Device Control",
43+
"Timing",
44+
"Data Storage",
45+
"Data Processing",
46+
"Other"
47+
]
48+
},
49+
"url": {
50+
"type": "string",
51+
"format": "uri"
52+
},
53+
"architectures": {
54+
"type": "string",
55+
"minLength": 1
56+
},
57+
"depends": {
58+
"type": "string",
59+
"pattern": "^[a-zA-Z][a-zA-Z\\d _\\.\\-,]*$"
60+
},
61+
"dot_a_linkage": {
62+
"type": "string",
63+
"enum": ["true", "false"]
64+
},
65+
"includes": {
66+
"type": "string",
67+
"minLength": 1
68+
},
69+
"precompiled": {
70+
"type": "string",
71+
"enum": ["true", "full", "false"]
72+
},
73+
"ldflags": {
74+
"type": "string"
75+
}
76+
},
77+
"propertyNames": {
78+
"not": {
79+
"$comment": "Misspelled optional property names",
80+
"pattern": "^((depend)|(D((epends?)|(EPENDS?)))|(dot_a_linkages)|(dot-?a-?linkages?)|(D(((ot)|(OT))[_-]?((a)|(A))[_-]?((linkages?)|(LINKAGES?))))|(include)|(I((ncludes?)|(NCLUDES?)))|(precompile)|(pre[-_]compiled?)|(P((re[-_]?compiled?)|(RE[-_]?COMPILED?)))|(ldflag)|(ld[-_]flags?)|(L((d[-_]?flags?)|(D[-_]?FLAGS?))))$"
81+
}
82+
},
83+
"required": ["name", "version", "author", "maintainer", "sentence", "paragraph", "category", "url", "architectures"]
84+
}

Diff for: check/check.go

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Package check runs checks on a project.
2+
package check
3+
4+
import (
5+
"fmt"
6+
"os"
7+
8+
"github.com/arduino/arduino-check/check/checkconfigurations"
9+
"github.com/arduino/arduino-check/check/checkdata"
10+
"github.com/arduino/arduino-check/configuration"
11+
"github.com/arduino/arduino-check/configuration/checkmode"
12+
"github.com/arduino/arduino-check/project"
13+
"github.com/arduino/arduino-check/result"
14+
"github.com/arduino/arduino-check/result/feedback"
15+
"github.com/arduino/arduino-check/result/outputformat"
16+
"github.com/sirupsen/logrus"
17+
)
18+
19+
// RunChecks runs all checks for the given project and outputs the results.
20+
func RunChecks(project project.Type) {
21+
fmt.Printf("Checking %s in %s\n", project.ProjectType, project.Path)
22+
23+
checkdata.Initialize(project)
24+
25+
for _, checkConfiguration := range checkconfigurations.Configurations() {
26+
runCheck, err := shouldRun(checkConfiguration, project)
27+
if err != nil {
28+
feedback.Errorf("Error while determining whether to run check: %v", err)
29+
os.Exit(1)
30+
}
31+
32+
if !runCheck {
33+
logrus.Infof("Skipping check: %s\n", checkConfiguration.ID)
34+
continue
35+
}
36+
37+
// Output will be printed after all checks are finished when configured for "json" output format
38+
if configuration.OutputFormat() == outputformat.Text {
39+
fmt.Printf("Running check %s: ", checkConfiguration.ID)
40+
}
41+
checkResult, checkOutput := checkConfiguration.CheckFunction()
42+
reportText := result.Results.Record(project, checkConfiguration, checkResult, checkOutput)
43+
if configuration.OutputFormat() == outputformat.Text {
44+
fmt.Print(reportText)
45+
}
46+
}
47+
48+
// Checks are finished for this project, so summarize its check results in the report.
49+
result.Results.AddProjectSummary(project)
50+
51+
if configuration.OutputFormat() == outputformat.Text {
52+
// Print the project check results summary.
53+
fmt.Print(result.Results.ProjectSummaryText(project))
54+
}
55+
}
56+
57+
// shouldRun returns whether a given check should be run for the given project under the current tool configuration.
58+
func shouldRun(checkConfiguration checkconfigurations.Type, currentProject project.Type) (bool, error) {
59+
configurationCheckModes := configuration.CheckModes(currentProject.SuperprojectType)
60+
61+
if checkConfiguration.ProjectType != currentProject.ProjectType {
62+
return false, nil
63+
}
64+
65+
for _, disableMode := range checkConfiguration.DisableModes {
66+
if configurationCheckModes[disableMode] {
67+
return false, nil
68+
}
69+
}
70+
71+
for _, enableMode := range checkConfiguration.EnableModes {
72+
if configurationCheckModes[enableMode] {
73+
return true, nil
74+
}
75+
}
76+
77+
// Use default
78+
for _, disableMode := range checkConfiguration.DisableModes {
79+
if disableMode == checkmode.Default {
80+
return false, nil
81+
}
82+
}
83+
84+
for _, enableMode := range checkConfiguration.EnableModes {
85+
if enableMode == checkmode.Default {
86+
return true, nil
87+
}
88+
}
89+
90+
return false, fmt.Errorf("Check %s is incorrectly configured", checkConfiguration.ID)
91+
}

Diff for: check/checkconfigurations/checkconfigurations.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
Package checkconfigurations defines the configuration of each check:
3+
- metadata
4+
- output template
5+
- under which conditions it's enabled
6+
- the level of a failure
7+
- which function implements it
8+
*/
9+
package checkconfigurations
10+
11+
import (
12+
"github.com/arduino/arduino-check/check/checkfunctions"
13+
"github.com/arduino/arduino-check/configuration/checkmode"
14+
"github.com/arduino/arduino-check/project/projecttype"
15+
)
16+
17+
// Type is the type for check configurations.
18+
type Type struct {
19+
ProjectType projecttype.Type // The project type the check applies to.
20+
// The following fields provide arbitrary text for the tool output associated with each check:
21+
Category string
22+
Subcategory string
23+
ID string // Unique check identifier: <project type identifier (L|S|P|I)><category identifier><number>
24+
Brief string // Short description of the check.
25+
Description string // Supplemental information about the check.
26+
MessageTemplate string // The warning/error message template displayed when the check fails. Will be filled by check function output.
27+
// The following fields define under which tool configuration modes the check will run:
28+
DisableModes []checkmode.Type // Check is disabled when tool is in any of these modes.
29+
EnableModes []checkmode.Type // Check is only enabled when tool is in one of these modes.
30+
// The following fields define the check level in each configuration mode:
31+
InfoModes []checkmode.Type // Failure of the check only results in an informational message.
32+
WarningModes []checkmode.Type // Failure of the check is considered a warning.
33+
ErrorModes []checkmode.Type // Failure of the check is considered an error.
34+
CheckFunction checkfunctions.Type // The function that implements the check.
35+
}
36+
37+
// Configurations returns the slice of check configurations.
38+
func Configurations() []Type {
39+
return configurations
40+
}
41+
42+
// configurations is an array of structs that define the configuration of each check.
43+
var configurations = []Type{
44+
{
45+
ProjectType: projecttype.Library,
46+
Category: "library.properties",
47+
Subcategory: "general",
48+
ID: "LP001",
49+
Brief: "invalid format",
50+
Description: "",
51+
MessageTemplate: "library.properties has an invalid format: {{.}}",
52+
DisableModes: nil,
53+
EnableModes: []checkmode.Type{checkmode.All},
54+
InfoModes: nil,
55+
WarningModes: nil,
56+
ErrorModes: []checkmode.Type{checkmode.All},
57+
CheckFunction: checkfunctions.LibraryPropertiesFormat,
58+
},
59+
{
60+
ProjectType: projecttype.Library,
61+
Category: "library.properties",
62+
Subcategory: "name field",
63+
ID: "LP002",
64+
Brief: "missing name field",
65+
Description: "",
66+
MessageTemplate: "missing name field in library.properties",
67+
DisableModes: nil,
68+
EnableModes: []checkmode.Type{checkmode.All},
69+
InfoModes: nil,
70+
WarningModes: nil,
71+
ErrorModes: []checkmode.Type{checkmode.All},
72+
CheckFunction: checkfunctions.LibraryPropertiesNameFieldMissing,
73+
},
74+
{
75+
ProjectType: projecttype.Library,
76+
Category: "library.properties",
77+
Subcategory: "name field",
78+
ID: "LP003",
79+
Brief: "disallowed characters",
80+
Description: "",
81+
MessageTemplate: "disallowed characters in library.properties name field. See: https://arduino.github.io/arduino-cli/latest/library-specification/#libraryproperties-file-format",
82+
DisableModes: nil,
83+
EnableModes: []checkmode.Type{checkmode.All},
84+
InfoModes: nil,
85+
WarningModes: nil,
86+
ErrorModes: []checkmode.Type{checkmode.All},
87+
CheckFunction: checkfunctions.LibraryPropertiesNameFieldDisallowedCharacters,
88+
},
89+
{
90+
ProjectType: projecttype.Library,
91+
Category: "library.properties",
92+
Subcategory: "version field",
93+
ID: "LP004",
94+
Brief: "missing version field",
95+
Description: "",
96+
MessageTemplate: "missing version field in library.properties",
97+
DisableModes: nil,
98+
EnableModes: []checkmode.Type{checkmode.All},
99+
InfoModes: nil,
100+
WarningModes: nil,
101+
ErrorModes: []checkmode.Type{checkmode.All},
102+
CheckFunction: checkfunctions.LibraryPropertiesVersionFieldMissing,
103+
},
104+
{
105+
ProjectType: projecttype.Sketch,
106+
Category: "structure",
107+
Subcategory: "",
108+
ID: "SS001",
109+
Brief: ".pde extension",
110+
Description: "The .pde extension is used by both Processing sketches and Arduino sketches. Processing sketches should either be in the \"data\" subfolder of the sketch or in the \"extras\" folder of the library. Arduino sketches should use the modern .ino extension",
111+
MessageTemplate: "{{.}} uses deprecated .pde file extension. Use .ino for Arduino sketches",
112+
DisableModes: nil,
113+
EnableModes: []checkmode.Type{checkmode.All},
114+
InfoModes: nil,
115+
WarningModes: []checkmode.Type{checkmode.Permissive},
116+
ErrorModes: []checkmode.Type{checkmode.Default},
117+
CheckFunction: checkfunctions.PdeSketchExtension,
118+
},
119+
}

Diff for: check/checkdata/checkdata.go

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
Package checkdata handles the collection of data specific to a project before running the checks on it.
3+
This is for data required by multiple checks.
4+
*/
5+
package checkdata
6+
7+
import (
8+
"github.com/arduino/arduino-check/project"
9+
"github.com/arduino/arduino-check/project/projecttype"
10+
"github.com/arduino/go-paths-helper"
11+
)
12+
13+
// Initialize gathers the check data for the specified project.
14+
func Initialize(project project.Type) {
15+
projectType = project.ProjectType
16+
projectPath = project.Path
17+
switch project.ProjectType {
18+
case projecttype.Sketch:
19+
case projecttype.Library:
20+
InitializeForLibrary(project)
21+
case projecttype.Platform:
22+
case projecttype.PackageIndex:
23+
}
24+
}
25+
26+
var projectType projecttype.Type
27+
28+
// ProjectType returns the type of the project being checked.
29+
func ProjectType() projecttype.Type {
30+
return projectType
31+
}
32+
33+
var projectPath *paths.Path
34+
35+
// ProjectPath returns the path to the project being checked.
36+
func ProjectPath() *paths.Path {
37+
return projectPath
38+
}

Diff for: check/checkdata/library.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package checkdata
2+
3+
import (
4+
"github.com/arduino/arduino-check/project"
5+
"github.com/arduino/arduino-check/project/library/libraryproperties"
6+
"github.com/arduino/go-properties-orderedmap"
7+
"github.com/xeipuuv/gojsonschema"
8+
)
9+
10+
// Initialize gathers the library check data for the specified project.
11+
func InitializeForLibrary(project project.Type) {
12+
libraryProperties, libraryPropertiesLoadError = libraryproperties.Properties(project.Path)
13+
if libraryPropertiesLoadError != nil {
14+
// TODO: can I even do this?
15+
libraryPropertiesSchemaValidationResult = nil
16+
} else {
17+
libraryPropertiesSchemaValidationResult = libraryproperties.Validate(libraryProperties)
18+
}
19+
}
20+
21+
var libraryPropertiesLoadError error
22+
23+
// LibraryPropertiesLoadError returns the error output from loading the library.properties metadata file.
24+
func LibraryPropertiesLoadError() error {
25+
return libraryPropertiesLoadError
26+
}
27+
28+
var libraryProperties *properties.Map
29+
30+
// LibraryProperties returns the data from the library.properties metadata file.
31+
func LibraryProperties() *properties.Map {
32+
return libraryProperties
33+
}
34+
35+
var libraryPropertiesSchemaValidationResult *gojsonschema.Result
36+
37+
// LibraryPropertiesSchemaValidationResult returns the result of validating library.properties against the JSON schema.
38+
// See: https://github.com/xeipuuv/gojsonschema
39+
func LibraryPropertiesSchemaValidationResult() *gojsonschema.Result {
40+
return libraryPropertiesSchemaValidationResult
41+
}

Diff for: check/checkfunctions/checkfunctions.go

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Package checkfunctions contains the functions that implement each check.
2+
package checkfunctions
3+
4+
import (
5+
"github.com/arduino/arduino-check/check/checkresult"
6+
)
7+
8+
// Type is the function signature for the check functions.
9+
// The `output` result is the contextual information that will be inserted into the check's message template.
10+
type Type func() (result checkresult.Type, output string)

0 commit comments

Comments
 (0)