Skip to content

Commit

Permalink
Merge pull request #5 from tphoney/add_javascript_scanner
Browse files Browse the repository at this point in the history
(feat) add javascript scanner
  • Loading branch information
TP Honey authored Jun 30, 2022
2 parents f69ed37 + 268d1e8 commit c785e88
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"mode": "auto",
"program": "${workspaceFolder}",
"env": {
"PLUGIN_WORKING_DIRECTORY": "/home/tp/workspace/drone",
"PLUGIN_WORKING_DIRECTORY": "/home/tp/workspace/drone-ui",
},
},
]
Expand Down
8 changes: 3 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ module github.com/tphoney/best_practice
go 1.18

require (
github.com/Masterminds/semver v1.5.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/sirupsen/logrus v1.4.2
github.com/sirupsen/logrus v1.8.1
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
)

require (
github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
)
require golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect
15 changes: 7 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0=
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8=
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
10 changes: 9 additions & 1 deletion plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/tphoney/best_practice/outputter/dronebuild"
"github.com/tphoney/best_practice/scanner"
"github.com/tphoney/best_practice/scanner/golang"
"github.com/tphoney/best_practice/scanner/javascript"
"github.com/tphoney/best_practice/types"
)

Expand Down Expand Up @@ -42,7 +43,7 @@ func Exec(ctx context.Context, args *Args) error {
fmt.Println("working directory:", args.WorkingDirectory)
// setup requested scanners
if len(args.RequestedScanners) == 0 {
args.RequestedScanners = []string{golang.Name}
args.RequestedScanners = []string{javascript.Name, golang.Name}
}
scanners := make([]types.Scanner, 0)
for _, scannerName := range args.RequestedScanners {
Expand All @@ -54,6 +55,13 @@ func Exec(ctx context.Context, args *Args) error {
return err
}
scanners = append(scanners, g)
case javascript.Name:
// create golang scanner
g, err := javascript.New(javascript.WithWorkingDirectory(args.WorkingDirectory))
if err != nil {
return err
}
scanners = append(scanners, g)
default:
fmt.Printf("unknown scanner: %s\n", scannerName)
}
Expand Down
4 changes: 2 additions & 2 deletions scanner/golang/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func (sc *scannerConfig) lintCheck() (match bool, outputResults []types.Scanlet)
}

func (sc *scannerConfig) mainCheck() (match bool, outputResults []types.Scanlet) {
matches, err := scanner.WalkMatch(sc.workingDirectory, "main.go")
matches, err := scanner.FindMatchingFiles(sc.workingDirectory, "main.go")
if err == nil && len(matches) > 0 {
// we use the first one found
mainLocation := strings.TrimPrefix(matches[0], sc.workingDirectory)
Expand Down Expand Up @@ -195,7 +195,7 @@ func (sc *scannerConfig) mainCheck() (match bool, outputResults []types.Scanlet)
}

func (sc *scannerConfig) unitTestCheck() (match bool, outputResults []types.Scanlet) {
matches, err := scanner.WalkMatch(sc.workingDirectory, "*_test.go")
matches, err := scanner.FindMatchingFiles(sc.workingDirectory, "*_test.go")
if err == nil && len(matches) > 0 {
droneBuildResult := types.Scanlet{
Name: UnitTestCheck,
Expand Down
163 changes: 163 additions & 0 deletions scanner/javascript/javascript.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package javascript

import (
"context"
"fmt"
"os"
"path/filepath"

"github.com/tphoney/best_practice/outputter/dronebuild"
"github.com/tphoney/best_practice/scanner"
"github.com/tphoney/best_practice/types"
"golang.org/x/exp/slices"
)

type scannerConfig struct {
name string
description string
workingDirectory string
checksToRun []string
runAll bool
}

const (
packageLocation = "package.json"
Name = "javascript"
BuildCheck = "build_check"
TestCheck = "test_check"
LintCheck = "lint_check"
)

func New(opts ...Option) (types.Scanner, error) {
sc := new(scannerConfig)
sc.name = Name
sc.description = "checks for various javascript related best practices"
sc.runAll = true
// apply options
for _, opt := range opts {
opt(sc)
}

return sc, nil
}

func (sc *scannerConfig) Name() string {
return sc.name
}

func (sc *scannerConfig) Description() string {
return sc.description
}

func (sc *scannerConfig) AvailableChecks() []string {
return []string{BuildCheck}
}

func (sc *scannerConfig) Scan(ctx context.Context, requestedChecks []string) (returnVal []types.Scanlet, err error) {
// lets look for a package file in the directory
_, err = os.Stat(filepath.Join(sc.workingDirectory, packageLocation))
if err != nil {
// nothing to see here, lets leave
return returnVal, nil
}
var scriptMap map[string]interface{}
var dependencyMap map[string]interface{}
var reactVersion string
packageStruct, err := scanner.ReadJSONFile(filepath.Join(sc.workingDirectory, packageLocation))
if err == nil {
// look for declared scripts
if packageStruct["scripts"] != nil {
scriptMap = packageStruct["scripts"].(map[string]interface{})
}
if packageStruct["dependencies"] != nil {
dependencyMap = packageStruct["dependencies"].(map[string]interface{})
rawReactVersion := dependencyMap["react"].(string)
v, versionErr := scanner.ReturnVersionObject(rawReactVersion)
if versionErr != nil {
fmt.Printf("error parsing react version: %s\n", versionErr.Error())
}
reactVersion = fmt.Sprint(v.Major())
}
} else {
return returnVal, err
}
// check for build
if sc.runAll || slices.Contains(requestedChecks, BuildCheck) {
match, outputResults := sc.buildCheck(scriptMap, reactVersion)
if match {
returnVal = append(returnVal, outputResults...)
}
}
if sc.runAll || slices.Contains(requestedChecks, LintCheck) {
match, outputResults := sc.lintCheck(scriptMap, reactVersion)
if match {
returnVal = append(returnVal, outputResults...)
}
}
if sc.runAll || slices.Contains(requestedChecks, TestCheck) {
match, outputResults := sc.testCheck(scriptMap, reactVersion)
if match {
returnVal = append(returnVal, outputResults...)
}
}

return returnVal, nil
}

func (sc *scannerConfig) buildCheck(scriptMap map[string]interface{}, reactVersion string) (match bool, outputResults []types.Scanlet) {
if scriptMap["build"] != "" {
droneBuildResult := types.Scanlet{
Name: BuildCheck,
ScannerFamily: Name,
Description: "run npm build",
OutputRenderer: dronebuild.Name,
Spec: dronebuild.OutputFields{
RawYaml: fmt.Sprintf(` - name: run npm build
image: node:%s-alpine
commands:
- npm run build`, reactVersion)},
}
outputResults = append(outputResults, droneBuildResult)
return true, outputResults
}
return false, outputResults
}

func (sc *scannerConfig) lintCheck(scriptMap map[string]interface{}, reactVersion string) (match bool, outputResults []types.Scanlet) {
if scriptMap["lint"] != "" {
droneBuildResult := types.Scanlet{
Name: BuildCheck,
ScannerFamily: Name,
Description: "run npm lint",
OutputRenderer: dronebuild.Name,
Spec: dronebuild.OutputFields{
RawYaml: fmt.Sprintf(` - name: run npm lint
image: node:%s-alpine
commands:
- npm run lint`, reactVersion)},
}
outputResults = append(outputResults, droneBuildResult)
return true, outputResults
}
return false, outputResults
}

func (sc *scannerConfig) testCheck(scriptMap map[string]interface{}, reactVersion string) (match bool, outputResults []types.Scanlet) {
if scriptMap["test"] != "" {
droneBuildResult := types.Scanlet{
Name: BuildCheck,
ScannerFamily: Name,
Description: "run npm test",
OutputRenderer: dronebuild.Name,
Spec: dronebuild.OutputFields{
RawYaml: fmt.Sprintf(` - name: run npm test
image: node:%s-alpine
commands:
- npm run test`, reactVersion)},
}
outputResults = append(outputResults, droneBuildResult)
return true, outputResults
}

return false, outputResults
}
29 changes: 29 additions & 0 deletions scanner/javascript/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package javascript

import "golang.org/x/exp/slices"

type Option func(*scannerConfig)

func WithChecksToRun(i []string) Option {
return func(p *scannerConfig) {
if len(i) > 0 {
validChecks := []string{}
// only add valid checks
for _, check := range i {
if slices.Contains(p.AvailableChecks(), check) {
validChecks = append(validChecks, check)
}
}
p.runAll = false
p.checksToRun = validChecks
} else {
p.runAll = true
}
}
}

func WithWorkingDirectory(i string) Option {
return func(p *scannerConfig) {
p.workingDirectory = i
}
}
31 changes: 30 additions & 1 deletion scanner/util.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package scanner

import (
"encoding/json"
"os"
"path/filepath"
"strings"

"github.com/Masterminds/semver"
)

func WalkMatch(workingDir, pattern string) ([]string, error) {
func FindMatchingFiles(workingDir, pattern string) ([]string, error) {
var matches []string
err := filepath.Walk(workingDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
Expand All @@ -26,3 +30,28 @@ func WalkMatch(workingDir, pattern string) ([]string, error) {
}
return matches, nil
}

func ReadJSONFile(filePath string) (map[string]interface{}, error) {
file, fileErr := os.Open(filePath)
if fileErr != nil {
return nil, fileErr
}
defer file.Close()
myMap := map[string]interface{}{}
decoder := json.NewDecoder(file)
jsonErr := decoder.Decode(&myMap)
if jsonErr != nil {
return nil, jsonErr
}
return myMap, nil
}

func ReturnVersionObject(version string) (*semver.Version, error) {
// strip weird stuff from the version slow for now
version = strings.ReplaceAll(version, "^", "")
v, err := semver.NewVersion(version)
if err != nil {
return nil, err
}
return v, nil
}

0 comments on commit c785e88

Please sign in to comment.