-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
819 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
"""`linter` module contains Linter class implementation.""" | ||
|
||
from __future__ import annotations | ||
|
||
from typing import Iterable | ||
|
||
from pygerber.gerber.ast.nodes.file import File | ||
from pygerber.gerber.linter.event_ast_visitor import EventAstVisitor | ||
from pygerber.gerber.linter.rule_violation import RuleViolation | ||
from pygerber.gerber.linter.rules.rule import Rule | ||
from pygerber.gerber.linter.violation_collector import ViolationCollector | ||
|
||
|
||
class Linter: | ||
"""Linter class implements high level linting API for Gerber files.""" | ||
|
||
def __init__(self, rules: list[Rule]) -> None: | ||
"""Initialize the Linter object.""" | ||
self.rules = rules | ||
|
||
def _register_rule(self, rule: Rule) -> None: | ||
"""Register a rule with the linter.""" | ||
rule.bind_rule_to_ast_visitor(self.event_ast_visitor) | ||
rule.bind_rule_to_violation_collector(self.violation_collector) | ||
|
||
def lint(self, ast: File) -> Iterable[RuleViolation]: | ||
"""Lint the AST and return a object containing all violations.""" | ||
self.violation_collector = ViolationCollector() | ||
self.event_ast_visitor = EventAstVisitor() | ||
|
||
for rule in self.rules: | ||
self._register_rule(rule) | ||
|
||
ast.visit(self.event_ast_visitor) | ||
return self.violation_collector.violations |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
"""`rule_violation` contains the RuleViolation class.""" | ||
|
||
from __future__ import annotations | ||
|
||
from pydantic import BaseModel | ||
|
||
|
||
class RuleViolation(BaseModel): | ||
"""Represents a rule violation detected by the linter.""" | ||
|
||
rule_id: str | ||
title: str | ||
description: str | ||
start_offset: int | ||
end_offset: int |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
"""`DEP001` module contains linter rule DEP001 implementation.""" # noqa: N999 | ||
|
||
from __future__ import annotations | ||
|
||
from pygerber.gerber.ast.nodes import G54 | ||
from pygerber.gerber.linter.rules.rule import register_rule | ||
from pygerber.gerber.linter.rules.static_rule import StaticRule | ||
|
||
|
||
@register_rule | ||
class DEP001(StaticRule): | ||
"""Rule DEP001 class implements a specific linting rule.""" | ||
|
||
rule_id = "DEP001" | ||
message = "Use of deprecated G54 code." | ||
description = ( | ||
"This historic code optionally precedes an aperture " | ||
"selection Dnn command. It has no effect. " | ||
"Sometimes used. Deprecated in 2012." | ||
) | ||
trigger_nodes = (G54,) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
"""`GRB001` module contains linter rule GRB001 implementation.""" # noqa: N999 | ||
|
||
from __future__ import annotations | ||
|
||
|
||
class GRB001: | ||
"""Rule GRB001 class implements a specific linting rule.""" | ||
|
||
rule_id = "GRB001" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""`rules` package contains all the linting rules for Gerber files.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
"""`rule` module contains the Rule class.""" | ||
|
||
from __future__ import annotations | ||
|
||
from abc import ABC, abstractmethod | ||
from typing import ClassVar, Optional, TypeVar | ||
|
||
from pygerber.gerber.ast.nodes import Node | ||
from pygerber.gerber.linter.event_ast_visitor import EventAstVisitor | ||
from pygerber.gerber.linter.rule_violation import RuleViolation | ||
from pygerber.gerber.linter.violation_collector import ViolationCollector | ||
|
||
|
||
class Rule(ABC): | ||
"""Base class for Gerber linter rules.""" | ||
|
||
rule_id: ClassVar[str] | ||
collector: Optional[ViolationCollector] = None | ||
|
||
@abstractmethod | ||
def get_violation_title(self) -> str: | ||
"""Return a title of message that describes the rule violation.""" | ||
|
||
@abstractmethod | ||
def get_violation_description(self) -> str: | ||
"""Return a description of the rule violation.""" | ||
|
||
@abstractmethod | ||
def get_trigger_nodes(self) -> list[type[Node]]: | ||
"""Return a list of node names that trigger the rule.""" | ||
|
||
def bind_rule_to_ast_visitor(self, visitor: EventAstVisitor) -> None: | ||
"""Bind the rule to the visitor.""" | ||
for node_type in self.get_trigger_nodes(): | ||
visitor.register_listener(node_type, self.node_callback) | ||
|
||
def bind_rule_to_violation_collector(self, collector: ViolationCollector) -> None: | ||
"""Bind the rule to the violation collector.""" | ||
self.collector = collector | ||
|
||
def report_violation(self, start_offset: int, end_offset: int) -> None: | ||
"""Report a violation.""" | ||
if self.collector is not None: | ||
violation = RuleViolation( | ||
rule_id=self.rule_id, | ||
title=self.get_violation_title(), | ||
description=self.get_violation_description(), | ||
start_offset=start_offset, | ||
end_offset=end_offset, | ||
) | ||
self.collector.add_violation(violation) | ||
|
||
@abstractmethod | ||
def node_callback(self, node: Node) -> None: | ||
"""Check the node for violations.""" | ||
|
||
|
||
RULE_REGISTRY: dict[str, type[Rule]] = {} | ||
|
||
T = TypeVar("T", bound="Rule") | ||
|
||
|
||
def register_rule(rule: type[T]) -> type[T]: | ||
"""Register a rule with the linter.""" | ||
assert rule.rule_id not in RULE_REGISTRY, f"Rule {rule.rule_id} already registered." | ||
RULE_REGISTRY[rule.rule_id] = rule | ||
return rule |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
"""`static_rule` module contains definition of StaticRule class.""" | ||
|
||
from __future__ import annotations | ||
|
||
from pygerber.gerber.ast.nodes import Node | ||
from pygerber.gerber.linter.rules.rule import Rule | ||
|
||
|
||
class StaticRule(Rule): | ||
"""StaticRule class is a base class for simple rules requiring no dynamic messages | ||
and no logic except boolean triggered/not triggered check. | ||
""" | ||
|
||
title: str | ||
message: str | ||
trigger_nodes: tuple[type[Node]] | ||
|
||
def get_violation_title(self) -> str: | ||
"""Return a title of message that describes the rule violation.""" | ||
return self.title | ||
|
||
def get_violation_description(self) -> str: | ||
"""Return a description of the rule violation.""" | ||
return self.message | ||
|
||
def get_trigger_nodes(self) -> list[type[Node]]: | ||
"""Return a list of node names that trigger the rule.""" | ||
return list(self.trigger_nodes) | ||
|
||
def node_callback(self, node: Node) -> None: | ||
"""Check the node for violations.""" | ||
self.report_violation( | ||
start_offset=( | ||
node.source_info.location if node.source_info is not None else 0 | ||
), | ||
end_offset=( | ||
node.source_info.location + node.source_info.length | ||
if node.source_info is not None | ||
else 0 | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
"""`violation_collector` module contains the ViolationCollector class.""" | ||
|
||
from __future__ import annotations | ||
|
||
from pygerber.gerber.linter.rule_violation import RuleViolation | ||
|
||
|
||
class ViolationCollector: | ||
"""ViolationCollector class is a container for RuleViolations.""" | ||
|
||
def __init__(self) -> None: | ||
"""Initialize the ViolationCollector object.""" | ||
self.violations: list[RuleViolation] = [] | ||
|
||
def add_violation(self, violation: RuleViolation) -> None: | ||
"""Add a violation to the collector.""" | ||
self.violations.append(violation) |