Skip to content

Commit

Permalink
Refactored Linting-Engine to Workspace modell and Resolver-Engine and…
Browse files Browse the repository at this point in the history
… added support for @-Notations and fixed a lot of linter render and linking bugs. Added a lot of tooltips.
  • Loading branch information
torbenschinke committed Jul 4, 2023
1 parent e0a286d commit 080e2df
Show file tree
Hide file tree
Showing 57 changed files with 1,577 additions and 405 deletions.
68 changes: 41 additions & 27 deletions linter/ambiguous.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,56 @@
package linter

import "github.com/worldiety/dddl/parser"
import (
"fmt"
"github.com/worldiety/dddl/parser"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)

type AmbiguousDeclaration struct {
hint
Declarations []parser.Declaration
}

// CheckAmbiguous validates that defined terms (e.g. Context, Workflow and Data)
// are all unique.
func CheckAmbiguous(root parser.Node) []Hint {
allDefs := map[string]parser.Node{}
var res []Hint
_ = parser.Walk(root, func(n parser.Node) error {
var parent *parser.Ident
var node parser.Node

switch n := n.(type) {
case *parser.Data:
parent = n.Name
node = n
case *parser.Workflow:
parent = n.Name
node = n
case *parser.Context:
parent = n.Name
node = n
}
ws := parser.WorkspaceOf(root)
if ws == nil {
return nil
}

if parent != nil {
if other, ok := allDefs[parent.Value]; ok {
res = append(res, Hint{
ParentIdent: parent,
Node: other,
Message: "Der Begriff %s wurde mehrfach definiert.",
})
} else {
allDefs[parent.Value] = node
allDefs := map[string][]parser.Declaration{}
err := parser.Walk(root, func(n parser.Node) error {
if decl, ok := n.(parser.Declaration); ok {
if _, isCtx := decl.(*parser.Context); isCtx {
return nil
}

q, ok := ws.Resolve(decl.DeclaredName())
if ok {
list := allDefs[q.String()]
list = append(list, decl)
allDefs[q.String()] = list
}
}

return nil
})

if err != nil {
panic(fmt.Errorf("cannot happen: %w", err))
}

var res []Hint
keys := maps.Keys(allDefs)
slices.Sort(keys)
for _, key := range keys {
list := allDefs[key]
if len(list) > 1 {
res = append(res, &AmbiguousDeclaration{Declarations: list})
}
}

return res
}
152 changes: 152 additions & 0 deletions linter/assignees.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package linter

import (
"fmt"
"github.com/worldiety/dddl/parser"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"regexp"
"strings"
)

var regexAssignee = regexp.MustCompile(`@\w+:?\s[^.?!\n]+`)
var regexName = regexp.MustCompile(`@\w+:?`)

type AssignedTask struct {
Assignee string
Task string
}

func ParseAssignees(str string) []AssignedTask {
var res []AssignedTask
matches := regexAssignee.FindAllString(str, -1)
for _, match := range matches {
names := regexName.FindAllString(match, 1)
if len(names) > 0 {
name := names[0][1:] // cut of @
if strings.HasSuffix(name, ":") {
name = name[:len(name)-1]
}
res = append(res, AssignedTask{
Assignee: name,
Task: strings.TrimSpace(match[len(names[0]):]),
})
}
}

return res
}

type AssignedContext struct {
hint
Task AssignedTask
Context *parser.Context // from to-do or definition
}

type AssignedData struct {
hint
Task AssignedTask
Data *parser.Data // from to-do or definition
}

type AssignedWorkflow struct {
hint
Task AssignedTask
Workflow *parser.Workflow // from to-do or definition or nested to-do within the actual workflow
}

type AssignedTasks struct {
hint
Assignee string
Contexts []AssignedContext
Workflows []AssignedWorkflow
Datas []AssignedData
}

// CheckAssignees inspects all definitions and Todos for assigned tasks.
func CheckAssignees(root parser.Node) []Hint {
var res []Hint
clustered := map[string]*AssignedTasks{}
getter := func(task AssignedTask) *AssignedTasks {
tmp := clustered[task.Assignee]
if tmp == nil {
tmp = &AssignedTasks{Assignee: task.Assignee}
clustered[task.Assignee] = tmp
}

return tmp
}

err := parser.Walk(root, func(n parser.Node) error {
switch n := n.(type) {
case *parser.Context:
for _, task := range ParseAssignees(n.Definition.Value()) {
tmp := getter(task)
tmp.Contexts = append(tmp.Contexts, AssignedContext{Task: task, Context: n})
res = append(res, &AssignedContext{Task: task, Context: n})
}

for _, task := range ParseAssignees(n.ToDo.Value()) {
tmp := getter(task)
tmp.Contexts = append(tmp.Contexts, AssignedContext{Task: task, Context: n})
res = append(res, &AssignedContext{Task: task, Context: n})
}

case *parser.Workflow:
for _, task := range ParseAssignees(n.Definition.Value()) {
tmp := getter(task)
tmp.Workflows = append(tmp.Workflows, AssignedWorkflow{Task: task, Workflow: n})
res = append(res, &AssignedWorkflow{Task: task, Workflow: n})
}

for _, task := range ParseAssignees(n.ToDo.Value()) {
tmp := getter(task)
tmp.Workflows = append(tmp.Workflows, AssignedWorkflow{Task: task, Workflow: n})
res = append(res, &AssignedWorkflow{Task: task, Workflow: n})
}

err := parser.Walk(n.Block, func(node parser.Node) error {
if todo, ok := node.(*parser.ToDo); ok {
for _, task := range ParseAssignees(todo.Value()) {
tmp := getter(task)
tmp.Workflows = append(tmp.Workflows, AssignedWorkflow{Task: task, Workflow: n})
res = append(res, &AssignedWorkflow{Task: task, Workflow: n})
}
}

return nil
})

if err != nil {
return err
}

case *parser.Data:
for _, task := range ParseAssignees(n.Definition.Value()) {
tmp := getter(task)
tmp.Datas = append(tmp.Datas, AssignedData{Task: task, Data: n})
res = append(res, &AssignedData{Task: task, Data: n})
}

for _, task := range ParseAssignees(n.ToDo.Value()) {
tmp := getter(task)
tmp.Datas = append(tmp.Datas, AssignedData{Task: task, Data: n})
res = append(res, &AssignedData{Task: task, Data: n})
}
}

return nil
})

if err != nil {
panic(fmt.Errorf("cannot happen: %w", err))
}

keys := maps.Keys(clustered)
slices.Sort(keys)
for _, key := range keys {
res = append(res, clustered[key])
}

return res
}
66 changes: 40 additions & 26 deletions linter/described.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,67 @@ import (
"github.com/worldiety/dddl/parser"
)

type ContextNotDescribed struct {
hint
Context *parser.Context
}

type ContextHasQuestions struct {
hint
Context *parser.Context
}

type WorkflowHasQuestions struct {
hint
Workflow *parser.Workflow
}

type WorkflowNotDescribed struct {
hint
Workflow *parser.Workflow
}

type DataNotDescribed struct {
hint
Data *parser.Data
}

type DataHasQuestions struct {
hint
Data *parser.Data
}

// CheckLiteralDefinitions inspects the "Definition" literals for types.
// Every parser.TypeDefinition should have one, otherwise
// Every parser.TypeDecl should have one, otherwise
// the ubiquitous language is incomplete.
func CheckLiteralDefinitions(root parser.Node) []Hint {
var res []Hint
err := parser.Walk(root, func(n parser.Node) error {
switch n := n.(type) {
case *parser.Context:
if n.Definition.Empty() {
res = append(res, Hint{
ParentIdent: n.Name,
Node: n.Definition,
Message: "Die Beschreibung des Kontexts %s fehlt.",
})
res = append(res, &ContextNotDescribed{Context: n})
}

if n.Definition.NeedsRevise() {
res = append(res, &ContextHasQuestions{Context: n})
}
case *parser.Workflow:
if n.Definition.Empty() {
res = append(res, Hint{
ParentIdent: n.Name,
Node: n.Definition,
Message: "Die Beschreibung des Arbeitsablaufs %s fehlt.",
})
res = append(res, &WorkflowNotDescribed{Workflow: n})
}

if n.Definition.NeedsRevise() {
res = append(res, Hint{
ParentIdent: n.Name,
Node: n.Definition,
Message: "Die Beschreibung des Arbeitsablaufs %s enthält noch ungeklärte Fragen.",
})
res = append(res, &WorkflowHasQuestions{Workflow: n})
}

case *parser.Data:
if n.Definition.Empty() {
res = append(res, Hint{
ParentIdent: n.Name,
Node: n.Definition,
Message: "Die Beschreibung zu den Daten %s fehlt.",
})
res = append(res, &DataNotDescribed{Data: n})
}

if n.Definition.NeedsRevise() {
res = append(res, Hint{
ParentIdent: n.Name,
Node: n.Definition,
Message: "Die Beschreibung zu den Daten %s enthält noch ungeklärte Fragen.",
})
res = append(res, &DataHasQuestions{Data: n})
}
}

Expand Down
34 changes: 7 additions & 27 deletions linter/linter.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
package linter

import (
"fmt"
"github.com/worldiety/dddl/parser"
"strings"
)

type Hint struct {
ParentIdent *parser.Ident // the identifier of the parent, e.g. for linking
Node parser.Node // the affected nearest node, e.g. for marking a line
Message string // the message to display
type hint struct {
}

func (h Hint) String(render func(ident *parser.Ident) string) string {
if strings.Contains(h.Message, "%s") {
return fmt.Sprintf(h.Message, render(h.ParentIdent))
}
func (h *hint) Hint() bool {
return true
}

return h.Message
type Hint interface {
Hint() bool
}

// Lint applies all available linters.
Expand All @@ -27,22 +22,7 @@ func Lint(root parser.Node) []Hint {
res = append(res, CheckLiteralDefinitions(root)...)
res = append(res, CheckUndefined(root)...)
res = append(res, CheckAmbiguous(root)...)

return Unique(res)
}

func Unique(hints []Hint) []Hint {
var res []Hint
tmp := map[string]struct{}{}
for _, hint := range hints {
key := hint.Message + hint.ParentIdent.Value
if _, ok := tmp[key]; ok {
continue
}

tmp[key] = struct{}{}
res = append(res, hint)
}
res = append(res, CheckAssignees(root)...)

return res
}
Loading

0 comments on commit 080e2df

Please sign in to comment.