Class: Archimate::Lint::Linter

Inherits:
Object
  • Object
show all
Defined in:
lib/archimate/lint/linter.rb

Overview

lint notes

x

Unused element (not used in a view)

x

Unused relation (not used in a view)

x

Object in a Viewpoint (invalid object for viewpoint)

x

Duplicate same name for type

x

empty view

?

visual nesting without relationship

Warn about sketch model

x

warn about names with (copy)

x

blank element names

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model) ⇒ Linter

Returns a new instance of Linter.



18
19
20
21
# File 'lib/archimate/lint/linter.rb', line 18

def initialize(model)
  @model = model
  @indent = "\n" + " " * 8
end

Instance Attribute Details

#modelObject (readonly)

Returns the value of attribute model.



16
17
18
# File 'lib/archimate/lint/linter.rb', line 16

def model
  @model
end

Instance Method Details

#duplicate_elementsObject



69
70
71
# File 'lib/archimate/lint/linter.rb', line 69

def duplicate_elements
  duplicates(model.elements, ->(el) { [el.type, name_for_comparison(el.name)] })
end

#duplicate_relationshipsObject



73
74
75
# File 'lib/archimate/lint/linter.rb', line 73

def duplicate_relationships
  duplicates(model.relationships, ->(el) { [el.type, el.source, el.target, name_for_comparison(el.name)] })
end

#duplicates(array, group_by_proc) ⇒ Object



81
82
83
84
85
86
87
# File 'lib/archimate/lint/linter.rb', line 81

def duplicates(array, group_by_proc)
  array
    .group_by { |el| group_by_proc.call(el) }
    .each_with_object([]) do |(_key, ary), dupes|
      dupes << ary.map(&:to_s) if ary.size > 1
    end
end

#empty_viewsObject



65
66
67
# File 'lib/archimate/lint/linter.rb', line 65

def empty_views
  model.diagrams.select { |diagram| diagram.nodes.empty? }
end

#entity_naming_rulesObject



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/archimate/lint/linter.rb', line 128

def entity_naming_rules
  model.elements.each_with_object([]) do |entity, errors|
    if entity.name.nil? || entity.name.empty?
      errors << "#{entity} name is empty"
    elsif entity.name.size < 2
      errors << "#{entity} name #{entity.name} is too short"
    elsif entity.name.include?("(copy)")
      errors << "#{entity} name #{entity.name} contains '(copy)'"
    end
  end
end

#format_issue(issue) ⇒ Object



42
43
44
45
46
# File 'lib/archimate/lint/linter.rb', line 42

def format_issue(issue)
  ary = Array(issue)
  return issue if ary.size == 1
  "#{@indent}#{ary.join(@indent)}"
end

#invalid_for_viewpointObject



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/archimate/lint/linter.rb', line 89

def invalid_for_viewpoint
  model.diagrams.each_with_object([]) do |diagram, errors|
    next if diagram.total_viewpoint?
    valid_entity_types = diagram.viewpoint&.allowed_element_types || DataModel::Elements.classes
    valid_relation_types = diagram.viewpoint&.allowed_relationship_types || DataModel::Relationships.classes
    invalid_elements = diagram.all_nodes.reject do |child|
      child.element.nil? || valid_entity_types.include?(child.element.class)
    end
    invalid_relations = diagram.connections.reject do |sc|
      sc.element.nil? || valid_relation_types.include?(sc.element.class)
    end
    next unless !invalid_elements.empty? || !invalid_relations.empty?
    errors << format(
      "%s viewpoint %s#{@indent}%s",
      diagram,
      diagram.viewpoint,
      (invalid_elements + invalid_relations).map(&:element).map(&:to_s).join(@indent)
    )
  end
end

#name_for_comparison(name) ⇒ Object



77
78
79
# File 'lib/archimate/lint/linter.rb', line 77

def name_for_comparison(name)
  name&.strip&.downcase
end

#nesting_without_relationObject



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/archimate/lint/linter.rb', line 110

def nesting_without_relation
  model
    .diagrams
    .flat_map(&:all_nodes) # These are top level view nodes
    .reject { |view_node| !view_node.element || view_node.nodes.empty? } # reject the ones with no child view nodes
    .flat_map { |parent|
      parent
        .nodes
        .select(&:element)
        .map { |child| [parent, child] } } # map into parent-child pairs for children that are elements
    .select { |pair|
      model.relationships.none? { |rel|
        (rel.source.equal?(pair[0]) && rel.target.equal?(pair[1]))
      }
    }
    .map { |pair| "#{pair[1].element} should not nest in #{pair[0].element} without valid relationship"}
end

#report(output_io) ⇒ Object

TODO: consider a means to sort lint issues by entity instead of lint category

this would point out the particularly problematic items that could be
dealt with appropriately.


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/archimate/lint/linter.rb', line 26

def report(output_io)
  [
    [unused_elements, "Unused Element"],
    [duplicate_elements, "Duplicate Items"],
    [entity_naming_rules, "Naming Rules"],
    [unused_relationships, "Unused Relationship"],
    [duplicate_relationships, "Duplicate Relationships"],
    [empty_views, "Empty View"],
    [invalid_for_viewpoint, "Invalid Type for Viewpoint"],
    [nesting_without_relation, "Visual Nesting"]
  ]
    .map { |issues, title| issues.map { |issue| "#{title}: #{format_issue(issue)}" } }
    .tap { |issues| report_subsection(issues, output_io) }
    .tap { |issues| output_io.puts("Total Issues: #{issues.flatten.size}") }
end

#report_subsection(issues, output_io) ⇒ Object



48
49
50
51
52
53
# File 'lib/archimate/lint/linter.rb', line 48

def report_subsection(issues, output_io)
  issues.each do |sub_issues|
    output_io.puts(sub_issues)
    output_io.puts("Sub Total: #{sub_issues.size}") unless sub_issues.empty?
  end
end

#unused_elementsObject



55
56
57
58
# File 'lib/archimate/lint/linter.rb', line 55

def unused_elements
  referenced_elements = model.diagrams.inject([]) { |memo, dia| memo.concat(dia.elements) }.uniq
  model.elements.reject { |el| referenced_elements.include?(el) }
end

#unused_relationshipsObject



60
61
62
63
# File 'lib/archimate/lint/linter.rb', line 60

def unused_relationships
  referenced_relationships = model.diagrams.inject([]) { |memo, dia| memo.concat(dia.relationships) }.uniq
  model.relationships.reject { |el| referenced_relationships.include?(el) }
end