Skip to content
This repository has been archived by the owner on Dec 8, 2022. It is now read-only.

feat(*): add SDK core #1

Merged
merged 26 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e095afe
feat(*): add SDK core
burntcarrot Jun 6, 2022
d768047
tests(*): integration tests using tree-sitter
burntcarrot Jun 7, 2022
5ad1b10
ci: add golangci-lint configuration
burntcarrot Jun 7, 2022
ceef642
fix(sdk/utils): sanitize markdown
burntcarrot Jun 7, 2022
bd4a0fe
refactor(*): refactor SDK and add tests
burntcarrot Jun 11, 2022
318ccaa
tests(*): test TOML generation
burntcarrot Jun 12, 2022
a0928fa
fix(utils): check return value while creating directory
burntcarrot Jun 12, 2022
da20033
refactor(*): refactor SDK and add tests
burntcarrot Jun 12, 2022
7ce40b7
docs(*): add guide for writing an analyzer
burntcarrot Jun 12, 2022
3a0c99a
chore: add README
burntcarrot Jun 12, 2022
f7d3b7e
refactor(*): refactor SDK and cleanup
burntcarrot Jun 13, 2022
6bd2540
fix(analyzers): use REPO_ROOT and TOOLBOX_PATH environment variables
burntcarrot Jun 14, 2022
a0ce3b1
chore: add gitignore
burntcarrot Jun 14, 2022
7fc2940
docs(*): update guide
burntcarrot Jun 14, 2022
adfb69d
docs(*): add guide for writing CSS analyzer
burntcarrot Jun 14, 2022
a101b86
tests(analysistest): add integration test for CSS
burntcarrot Jun 14, 2022
bb2135c
chore: fix DeepSource issues
burntcarrot Jun 14, 2022
180769b
refactor(*): refactor tests and add guides
burntcarrot Jun 15, 2022
e5a7554
docs(guides): fix TOML fields for issues.toml
burntcarrot Jun 15, 2022
a74d7ce
ci: install csslint
burntcarrot Jun 15, 2022
e9e4724
feat(analysistest): add all tree-sitter languages
burntcarrot Jun 15, 2022
f7d38a1
Apply suggestions from code review
sourya-deepsource Jun 15, 2022
0513907
tests(*): add unit tests
burntcarrot Jun 17, 2022
1034696
chore: fix merge conflicts
burntcarrot Jun 17, 2022
1112721
refactor(*): allow issue parsing via structs
burntcarrot Jun 17, 2022
ad43322
refactor(*): pass env vars through params
burntcarrot Jun 20, 2022
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
14 changes: 14 additions & 0 deletions .deepsource.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version = 1

exclude_patterns = ["**/testdata/src/**"]

[[analyzers]]
name = "go"
enabled = true

[analyzers.meta]
import_root = "github.com/deepsourcelabs/deepsource-go"

[[transformers]]
name = "gofumpt"
enabled = true
26 changes: 26 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Tests
on: [push, pull_request]
jobs:
tests:
name: tests
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [1.17.x]
steps:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 14
- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@latest
- name: Install csslint
run: npm install -g csslint
- name: Run tests
run: go test -v ./...
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.envrc
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<p align="center">
<img src="https://cms.deepsource.io/logo-wordmark-dark.svg" />
</p>

<p align="center">
<a href="https://deepsource.io/docs">Documentation</a> |
<a href="https://deepsource.io/signup">Get Started</a> |
<a href="https://discuss.deepsource.io/">Discuss</a>
</p>

<p align="center">
DeepSource helps you ship good quality code.
</p>

</p>

---

# DeepSource Go SDK

<a href="https://pkg.go.dev/github.com/deepsourcelabs/deepsource-go"><img src="https://godoc.org/github.com/deepsourcelabs/deepsource-go?status.svg" /></a>

Go SDK for [DeepSource](https://deepsource.io/).

The Go SDK makes it easier for developers to integrate an existing analyzer with DeepSource.

## Guides

Here are some extensive guides on working with the SDK:

- [Writing custom analyzers](guides/writing-analyzers.md)
- [Testing Analyzers](guides/testing-analyzers.md)
- [Writing a CSS Analyzer](guides/css-analyzer.md)
## Community

Interested in DeepSource and want to chat with the community? Feel free to join our [Discord server](http://deepsource.io/discord).
267 changes: 267 additions & 0 deletions analyzers/analysistest/analysistest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
package analysistest

import (
"context"
"encoding/json"
"errors"
"io/fs"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strings"

"github.com/deepsourcelabs/deepsource-go/analyzers/types"
sitter "github.com/smacker/go-tree-sitter"
"github.com/smacker/go-tree-sitter/bash"
"github.com/smacker/go-tree-sitter/c"
"github.com/smacker/go-tree-sitter/cpp"
"github.com/smacker/go-tree-sitter/csharp"
"github.com/smacker/go-tree-sitter/css"
"github.com/smacker/go-tree-sitter/elm"
"github.com/smacker/go-tree-sitter/golang"
"github.com/smacker/go-tree-sitter/hcl"
"github.com/smacker/go-tree-sitter/html"
"github.com/smacker/go-tree-sitter/java"
"github.com/smacker/go-tree-sitter/javascript"
"github.com/smacker/go-tree-sitter/lua"
"github.com/smacker/go-tree-sitter/ocaml"
"github.com/smacker/go-tree-sitter/php"
"github.com/smacker/go-tree-sitter/protobuf"
"github.com/smacker/go-tree-sitter/python"
"github.com/smacker/go-tree-sitter/ruby"
"github.com/smacker/go-tree-sitter/rust"
"github.com/smacker/go-tree-sitter/scala"
"github.com/smacker/go-tree-sitter/svelte"
"github.com/smacker/go-tree-sitter/toml"
"github.com/smacker/go-tree-sitter/typescript/typescript"
"github.com/smacker/go-tree-sitter/yaml"
)

// ParsedIssue represents an issue parsed using tree-sitter.
type ParsedIssue struct {
IssueCode string
Line int
}

func Run(directory string) error {
// read the generated report from TOOLBOX_PATH
toolboxPath := os.Getenv("TOOLBOX_PATH")
generatedFile := path.Join(toolboxPath, "analysis_report.json")
vishnu-deepsource marked this conversation as resolved.
Show resolved Hide resolved
reportContent, err := os.ReadFile(generatedFile)
if err != nil {
return err
}

var report types.AnalysisReport
err = json.Unmarshal(reportContent, &report)
if err != nil {
return err
}

// do a verification check for the generated report
err = verifyReport(report, directory)
if err != nil {
return err
}

// cleanup after test
err = os.Remove(generatedFile)
if err != nil {
return err
}

return nil
}

// getFilenames returns the filenames for a directory.
func getFilenames(directory string) ([]string, error) {
var files []string
filepath.Walk(directory, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}

// if not a directory, append to files
if !info.IsDir() {
filename := filepath.Join(directory, info.Name())
files = append(files, filename)
}

return nil
})

return files, nil
}

// Verify compares the generated report and parsed issues using tree-sitter.
func verifyReport(report types.AnalysisReport, directory string) error {
var parsedIssues []ParsedIssue

// get filenames
files, err := getFilenames(directory)
if err != nil {
return err
}

parser := sitter.NewParser()

// walk through each file and get issues
for _, filename := range files {
// get language
lang, err := getLanguage(filename)
if err != nil {
return err
}
parser.SetLanguage(lang)

// read report
content, err := os.ReadFile(filename)
if err != nil {
return err
}

// generate tree
ctx := context.Background()
burntcarrot marked this conversation as resolved.
Show resolved Hide resolved
tree, err := parser.ParseCtx(ctx, nil, content)
if err != nil {
return err
}

// create a query for fetching comments
queryStr := "(comment) @comment"
burntcarrot marked this conversation as resolved.
Show resolved Hide resolved
query, err := sitter.NewQuery([]byte(queryStr), lang)
if err != nil {
return err
}

// execute query on root node
qc := sitter.NewQueryCursor()
n := tree.RootNode()
qc.Exec(query, n)
defer qc.Close()
burntcarrot marked this conversation as resolved.
Show resolved Hide resolved

// iterate over matches
for {
m, ok := qc.NextMatch()
if !ok {
break
}

for _, c := range m.Captures {
// get node content
node := c.Node
nodeContent := node.Content(content)

// check if the comment contains raise annotation
if strings.Contains(nodeContent, "raise") {
// find match using expression
exp := regexp.MustCompile(`.+ raise: `)
submatches := exp.FindStringSubmatch(nodeContent)

if len(submatches) != 0 {
substrings := exp.Split(nodeContent, -1)
if len(substrings) > 1 {
issueCodes := strings.Split(substrings[1], ",")
// add issue to parsedIssues
for _, issueCode := range issueCodes {
parsedIssue := ParsedIssue{IssueCode: strings.TrimSpace(issueCode), Line: int(node.StartPoint().Row) + 1}
parsedIssues = append(parsedIssues, parsedIssue)
}
}
}
}
}
}
}
burntcarrot marked this conversation as resolved.
Show resolved Hide resolved

// if number of issues don't match, exit early.
if len(parsedIssues) != len(report.Issues) {
return errors.New("mismatch between the number of reported issues and parsed issues")
}

// compare the report's issues and parsed issues
match := compareReport(parsedIssues, report)
if !match {
return errors.New("mismatch between parsed issue and report issue")
}

return nil
}

// getLanguage is a helper for fetching a tree-sitter language based on the file's extension.
func getLanguage(filename string) (*sitter.Language, error) {
extension := filepath.Ext(filename)

switch extension {
case ".sh":
return bash.GetLanguage(), nil
case ".c":
return c.GetLanguage(), nil
case ".cpp":
return cpp.GetLanguage(), nil
case ".cs":
return csharp.GetLanguage(), nil
case ".css":
return css.GetLanguage(), nil
case ".elm":
return elm.GetLanguage(), nil
case ".go":
return golang.GetLanguage(), nil
case ".hcl":
return hcl.GetLanguage(), nil
case ".html":
return html.GetLanguage(), nil
case ".java":
return java.GetLanguage(), nil
case ".js":
return javascript.GetLanguage(), nil
case ".lua":
return lua.GetLanguage(), nil
case ".ml":
return ocaml.GetLanguage(), nil
case ".php":
return php.GetLanguage(), nil
case ".pb", ".proto":
return protobuf.GetLanguage(), nil
case ".py":
return python.GetLanguage(), nil
case ".rb":
return ruby.GetLanguage(), nil
case ".rs":
return rust.GetLanguage(), nil
case ".scala":
return scala.GetLanguage(), nil
case ".svelte":
return svelte.GetLanguage(), nil
case ".toml":
return toml.GetLanguage(), nil
case ".ts":
return typescript.GetLanguage(), nil
case ".yaml":
return yaml.GetLanguage(), nil
default:
return nil, errors.New("language not supported")
}
}

// compareReport is a helper which checks if the parsed issues are identical to the issues present in the report.
func compareReport(parsedIssues []ParsedIssue, report types.AnalysisReport) bool {
// sort report and parsedIssues by line number
sort.Slice(parsedIssues, func(i, j int) bool {
return parsedIssues[i].Line < parsedIssues[j].Line
})

sort.Slice(report.Issues, func(i, j int) bool {
return report.Issues[i].Location.Position.Begin.Line < report.Issues[j].Location.Position.Begin.Line
})

for i, issue := range report.Issues {
if (parsedIssues[i].Line != issue.Location.Position.Begin.Line) && (parsedIssues[i].IssueCode != issue.IssueCode) {
return false
}
}

return true
}
Loading