Skip to content

Create tool for linting Arduino projects #1

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

Merged
merged 54 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
1646215
Add first draft of library.properties schema
per1234 Sep 16, 2020
e17c4d7
Initial commit of code
per1234 Sep 17, 2020
ecdce06
Use consistent approach to handling custom types
per1234 Sep 20, 2020
12411ac
Rename projects package to project
per1234 Sep 20, 2020
b261e7b
Schema: update name pattern to new specification
per1234 Sep 21, 2020
4d024ed
Schema: remove the non-capturing group syntax from the version regex
per1234 Sep 21, 2020
89d6372
Schema: fix the misspelled optional field name regex
per1234 Sep 21, 2020
c04946b
Correctly handle "NotRun" check results
per1234 Sep 21, 2020
c39f587
Create libraryproperties package and add validation helper functions
per1234 Sep 21, 2020
bd5b370
Add "notice" checklevel
per1234 Sep 21, 2020
416233a
Rename "Skipped" checkresult to "Skip"
per1234 Sep 21, 2020
fce1f2c
Add reminder comment re: handling exit status
per1234 Sep 21, 2020
8b527e4
Add more checks
per1234 Sep 21, 2020
132ce6d
Schema: make name regex more minimal
per1234 Sep 21, 2020
abd360f
Split check functions into separate files according to project type
per1234 Sep 21, 2020
3682cee
Add check for .pde sketch file extension
per1234 Sep 21, 2020
ac1bec4
Output error messages from projects.FindProjects()
per1234 Sep 21, 2020
27a6787
Fix check configuration system
per1234 Sep 21, 2020
f88be58
Start setting up logging
per1234 Sep 21, 2020
19ee67b
Fix bug with project discovery system
per1234 Sep 21, 2020
26046fb
Provide meaningful output when checks don't run due to required data
per1234 Sep 21, 2020
ed14838
Add a TODO comment
per1234 Sep 21, 2020
2c5c11a
Set up configuration to be able to easily experiment with different s…
per1234 Sep 21, 2020
f8da280
Remove "Pass" check level
per1234 Sep 21, 2020
cd49ac3
Add/improve code comments
per1234 Sep 21, 2020
952112d
Add TODO comment re: coniguration.init() vs configuration.Initialize()
per1234 Sep 21, 2020
00178a7
Rename checkconfigurations.Type.Name field to checkconfigurations.Typ…
per1234 Sep 21, 2020
c1c3b9c
Rename configuration.SuperprojectType() to configuration.Superproject…
per1234 Sep 21, 2020
63a68f5
Rename project.findProjects() to project.findProjectsUnderPath()
per1234 Sep 21, 2020
8547011
Add a getter for checkconfigurations.configurations
per1234 Sep 21, 2020
f9027f8
Improve organization of code
per1234 Sep 21, 2020
42039d9
Return error when check run configuration is missing
per1234 Sep 21, 2020
658cdf6
Use packageindex.HasValidExtension() in package index detection code
per1234 Sep 22, 2020
b1a0224
Define supported and valid library examples folder names in the libra…
per1234 Sep 22, 2020
598ae7f
Define valid platform bundled libraries folder names in the platform …
per1234 Sep 22, 2020
e5133ae
Add go.mod files
Sep 22, 2020
8af60f3
Only print skipped check information to log
per1234 Sep 22, 2020
a93a6d0
Add support for "json" output format
per1234 Sep 22, 2020
528aa98
Set exit status according to check results
per1234 Sep 22, 2020
2c29553
Add support for report file option
per1234 Sep 22, 2020
9bde283
Return boolean variable values directly from functions
per1234 Oct 29, 2020
b3457a2
Reduce verbosity of Boolean comparisons
per1234 Oct 29, 2020
b40200e
Remove unncessary use of String() method
per1234 Oct 29, 2020
a842f06
Eliminate duplicate code in subproject discovery
per1234 Oct 29, 2020
2fe0250
Panic if subproject discovery was not configured for project type
per1234 Oct 29, 2020
918d845
Refactor .pde sketch check function's output code
per1234 Oct 29, 2020
2517a0b
Add doc comments for check functions
per1234 Oct 29, 2020
9ddc1bb
Add project type matcher method
per1234 Oct 29, 2020
e692238
Use more apropriate project type in log message
per1234 Oct 29, 2020
561d88e
Use more appropriate variable name for project type filter
per1234 Oct 29, 2020
b9f951b
Refactor project identification functions
per1234 Oct 29, 2020
fbe79ee
Support multiple report instances
per1234 Oct 29, 2020
6894049
Refactor names in the result package
per1234 Oct 29, 2020
09bbd6a
Use an "enum" for the output format types
per1234 Oct 29, 2020
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: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# arduino-check

`arduino-check` automatically checks for common problems in your [Arduino](https://www.arduino.cc/) projects:

- Sketches
- Libraries
84 changes: 84 additions & 0 deletions arduino-library-properties-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://github.com/arduino/arduino-check/arduino-library-properties-schema.json",
"title": "Arduino library.properties JSON schema",
"description": "library.properties is the metadata file for Arduino libraries. See: https://arduino.github.io/arduino-cli/latest/library-specification/#library-metadata",
"$comment": "For information on the Arduino library.properties format, see https://godoc.org/github.com/arduino/go-properties-orderedmap",
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 63,
"pattern": "^(([a-zA-Z][a-zA-Z0-9 _\\.\\-]*)|([0-9][a-zA-Z0-9 _\\.\\-]*[a-zA-Z][a-zA-Z0-9 _\\.\\-]*))$"
},
"version": {
"type": "string",
"$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",
"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-]+)*))?$"
},
"author": {
"type": "string",
"minLength": 1
},
"maintainer": {
"type": "string",
"minLength": 1
},
"sentence": {
"type": "string",
"minLength": 1
},
"paragraph": {
"type": "string"
},
"category": {
"type": "string",
"enum": [
"Display",
"Communication",
"Signal Input/Output",
"Sensors",
"Device Control",
"Timing",
"Data Storage",
"Data Processing",
"Other"
]
},
"url": {
"type": "string",
"format": "uri"
},
"architectures": {
"type": "string",
"minLength": 1
},
"depends": {
"type": "string",
"pattern": "^[a-zA-Z][a-zA-Z\\d _\\.\\-,]*$"
},
"dot_a_linkage": {
"type": "string",
"enum": ["true", "false"]
},
"includes": {
"type": "string",
"minLength": 1
},
"precompiled": {
"type": "string",
"enum": ["true", "full", "false"]
},
"ldflags": {
"type": "string"
}
},
"propertyNames": {
"not": {
"$comment": "Misspelled optional property names",
"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?))))$"
}
},
"required": ["name", "version", "author", "maintainer", "sentence", "paragraph", "category", "url", "architectures"]
}
91 changes: 91 additions & 0 deletions check/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Package check runs checks on a project.
package check

import (
"fmt"
"os"

"github.com/arduino/arduino-check/check/checkconfigurations"
"github.com/arduino/arduino-check/check/checkdata"
"github.com/arduino/arduino-check/configuration"
"github.com/arduino/arduino-check/configuration/checkmode"
"github.com/arduino/arduino-check/project"
"github.com/arduino/arduino-check/result"
"github.com/arduino/arduino-check/result/feedback"
"github.com/arduino/arduino-check/result/outputformat"
"github.com/sirupsen/logrus"
)

// RunChecks runs all checks for the given project and outputs the results.
func RunChecks(project project.Type) {
fmt.Printf("Checking %s in %s\n", project.ProjectType, project.Path)

checkdata.Initialize(project)

for _, checkConfiguration := range checkconfigurations.Configurations() {
runCheck, err := shouldRun(checkConfiguration, project)
if err != nil {
feedback.Errorf("Error while determining whether to run check: %v", err)
os.Exit(1)
}

if !runCheck {
logrus.Infof("Skipping check: %s\n", checkConfiguration.ID)
continue
}

// Output will be printed after all checks are finished when configured for "json" output format
if configuration.OutputFormat() == outputformat.Text {
fmt.Printf("Running check %s: ", checkConfiguration.ID)
}
checkResult, checkOutput := checkConfiguration.CheckFunction()
reportText := result.Results.Record(project, checkConfiguration, checkResult, checkOutput)
if configuration.OutputFormat() == outputformat.Text {
fmt.Print(reportText)
}
}

// Checks are finished for this project, so summarize its check results in the report.
result.Results.AddProjectSummary(project)

if configuration.OutputFormat() == outputformat.Text {
// Print the project check results summary.
fmt.Print(result.Results.ProjectSummaryText(project))
}
}

// shouldRun returns whether a given check should be run for the given project under the current tool configuration.
func shouldRun(checkConfiguration checkconfigurations.Type, currentProject project.Type) (bool, error) {
configurationCheckModes := configuration.CheckModes(currentProject.SuperprojectType)

if checkConfiguration.ProjectType != currentProject.ProjectType {
return false, nil
}

for _, disableMode := range checkConfiguration.DisableModes {
if configurationCheckModes[disableMode] {
return false, nil
}
}

for _, enableMode := range checkConfiguration.EnableModes {
if configurationCheckModes[enableMode] {
return true, nil
}
}

// Use default
for _, disableMode := range checkConfiguration.DisableModes {
if disableMode == checkmode.Default {
return false, nil
}
}

for _, enableMode := range checkConfiguration.EnableModes {
if enableMode == checkmode.Default {
return true, nil
}
}

return false, fmt.Errorf("Check %s is incorrectly configured", checkConfiguration.ID)
}
119 changes: 119 additions & 0 deletions check/checkconfigurations/checkconfigurations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
Package checkconfigurations defines the configuration of each check:
- metadata
- output template
- under which conditions it's enabled
- the level of a failure
- which function implements it
*/
package checkconfigurations

import (
"github.com/arduino/arduino-check/check/checkfunctions"
"github.com/arduino/arduino-check/configuration/checkmode"
"github.com/arduino/arduino-check/project/projecttype"
)

// Type is the type for check configurations.
type Type struct {
ProjectType projecttype.Type // The project type the check applies to.
// The following fields provide arbitrary text for the tool output associated with each check:
Category string
Subcategory string
ID string // Unique check identifier: <project type identifier (L|S|P|I)><category identifier><number>
Brief string // Short description of the check.
Description string // Supplemental information about the check.
MessageTemplate string // The warning/error message template displayed when the check fails. Will be filled by check function output.
// The following fields define under which tool configuration modes the check will run:
DisableModes []checkmode.Type // Check is disabled when tool is in any of these modes.
EnableModes []checkmode.Type // Check is only enabled when tool is in one of these modes.
// The following fields define the check level in each configuration mode:
InfoModes []checkmode.Type // Failure of the check only results in an informational message.
WarningModes []checkmode.Type // Failure of the check is considered a warning.
ErrorModes []checkmode.Type // Failure of the check is considered an error.
CheckFunction checkfunctions.Type // The function that implements the check.
}

// Configurations returns the slice of check configurations.
func Configurations() []Type {
return configurations
}

// configurations is an array of structs that define the configuration of each check.
var configurations = []Type{
{
ProjectType: projecttype.Library,
Category: "library.properties",
Subcategory: "general",
ID: "LP001",
Brief: "invalid format",
Description: "",
MessageTemplate: "library.properties has an invalid format: {{.}}",
DisableModes: nil,
EnableModes: []checkmode.Type{checkmode.All},
InfoModes: nil,
WarningModes: nil,
ErrorModes: []checkmode.Type{checkmode.All},
CheckFunction: checkfunctions.LibraryPropertiesFormat,
},
{
ProjectType: projecttype.Library,
Category: "library.properties",
Subcategory: "name field",
ID: "LP002",
Brief: "missing name field",
Description: "",
MessageTemplate: "missing name field in library.properties",
DisableModes: nil,
EnableModes: []checkmode.Type{checkmode.All},
InfoModes: nil,
WarningModes: nil,
ErrorModes: []checkmode.Type{checkmode.All},
CheckFunction: checkfunctions.LibraryPropertiesNameFieldMissing,
},
{
ProjectType: projecttype.Library,
Category: "library.properties",
Subcategory: "name field",
ID: "LP003",
Brief: "disallowed characters",
Description: "",
MessageTemplate: "disallowed characters in library.properties name field. See: https://arduino.github.io/arduino-cli/latest/library-specification/#libraryproperties-file-format",
DisableModes: nil,
EnableModes: []checkmode.Type{checkmode.All},
InfoModes: nil,
WarningModes: nil,
ErrorModes: []checkmode.Type{checkmode.All},
CheckFunction: checkfunctions.LibraryPropertiesNameFieldDisallowedCharacters,
},
{
ProjectType: projecttype.Library,
Category: "library.properties",
Subcategory: "version field",
ID: "LP004",
Brief: "missing version field",
Description: "",
MessageTemplate: "missing version field in library.properties",
DisableModes: nil,
EnableModes: []checkmode.Type{checkmode.All},
InfoModes: nil,
WarningModes: nil,
ErrorModes: []checkmode.Type{checkmode.All},
CheckFunction: checkfunctions.LibraryPropertiesVersionFieldMissing,
},
{
ProjectType: projecttype.Sketch,
Category: "structure",
Subcategory: "",
ID: "SS001",
Brief: ".pde extension",
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",
MessageTemplate: "{{.}} uses deprecated .pde file extension. Use .ino for Arduino sketches",
DisableModes: nil,
EnableModes: []checkmode.Type{checkmode.All},
InfoModes: nil,
WarningModes: []checkmode.Type{checkmode.Permissive},
ErrorModes: []checkmode.Type{checkmode.Default},
CheckFunction: checkfunctions.PdeSketchExtension,
},
}
38 changes: 38 additions & 0 deletions check/checkdata/checkdata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
Package checkdata handles the collection of data specific to a project before running the checks on it.
This is for data required by multiple checks.
*/
package checkdata

import (
"github.com/arduino/arduino-check/project"
"github.com/arduino/arduino-check/project/projecttype"
"github.com/arduino/go-paths-helper"
)

// Initialize gathers the check data for the specified project.
func Initialize(project project.Type) {
projectType = project.ProjectType
projectPath = project.Path
switch project.ProjectType {
case projecttype.Sketch:
case projecttype.Library:
InitializeForLibrary(project)
case projecttype.Platform:
case projecttype.PackageIndex:
}
}

var projectType projecttype.Type

// ProjectType returns the type of the project being checked.
func ProjectType() projecttype.Type {
return projectType
}

var projectPath *paths.Path

// ProjectPath returns the path to the project being checked.
func ProjectPath() *paths.Path {
return projectPath
}
41 changes: 41 additions & 0 deletions check/checkdata/library.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package checkdata

import (
"github.com/arduino/arduino-check/project"
"github.com/arduino/arduino-check/project/library/libraryproperties"
"github.com/arduino/go-properties-orderedmap"
"github.com/xeipuuv/gojsonschema"
)

// Initialize gathers the library check data for the specified project.
func InitializeForLibrary(project project.Type) {
libraryProperties, libraryPropertiesLoadError = libraryproperties.Properties(project.Path)
if libraryPropertiesLoadError != nil {
// TODO: can I even do this?
libraryPropertiesSchemaValidationResult = nil
} else {
libraryPropertiesSchemaValidationResult = libraryproperties.Validate(libraryProperties)
}
}

var libraryPropertiesLoadError error

// LibraryPropertiesLoadError returns the error output from loading the library.properties metadata file.
func LibraryPropertiesLoadError() error {
return libraryPropertiesLoadError
}

var libraryProperties *properties.Map

// LibraryProperties returns the data from the library.properties metadata file.
func LibraryProperties() *properties.Map {
return libraryProperties
}

var libraryPropertiesSchemaValidationResult *gojsonschema.Result

// LibraryPropertiesSchemaValidationResult returns the result of validating library.properties against the JSON schema.
// See: https://github.com/xeipuuv/gojsonschema
func LibraryPropertiesSchemaValidationResult() *gojsonschema.Result {
return libraryPropertiesSchemaValidationResult
}
10 changes: 10 additions & 0 deletions check/checkfunctions/checkfunctions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Package checkfunctions contains the functions that implement each check.
package checkfunctions

import (
"github.com/arduino/arduino-check/check/checkresult"
)

// Type is the function signature for the check functions.
// The `output` result is the contextual information that will be inserted into the check's message template.
type Type func() (result checkresult.Type, output string)
Loading