-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathregistry.py
More file actions
114 lines (93 loc) · 3.71 KB
/
Copy pathregistry.py
File metadata and controls
114 lines (93 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
from dataclasses import dataclass, asdict
from importlib.resources import files
from importlib.metadata import entry_points
from typing import Optional
from codemodder.executor import CodemodExecutorWrapper
from codemodder.logging import logger
@dataclass
class CodemodCollection:
"""A collection of codemods that all share the same origin and documentation."""
origin: str
docs_module: str
semgrep_config_module: str
codemods: list
class CodemodRegistry:
_codemods_by_name: dict[str, CodemodExecutorWrapper]
_codemods_by_id: dict[str, CodemodExecutorWrapper]
def __init__(self):
self._codemods_by_name = {}
self._codemods_by_id = {}
@property
def names(self):
return list(self._codemods_by_name.keys())
@property
def ids(self):
return list(self._codemods_by_id.keys())
@property
def codemods(self):
return list(self._codemods_by_name.values())
def add_codemod_collection(self, collection: CodemodCollection):
docs_module = files(collection.docs_module)
semgrep_module = files(collection.semgrep_config_module)
for codemod in collection.codemods:
self._validate_codemod(codemod)
wrapper = CodemodExecutorWrapper(
codemod,
collection.origin,
docs_module,
semgrep_module,
)
self._codemods_by_name[wrapper.name] = wrapper
self._codemods_by_id[wrapper.id] = wrapper
def _validate_codemod(self, codemod):
for name in ["SUMMARY", "METADATA"]:
if not (attr := getattr(codemod, name)) or attr is NotImplemented:
raise ValueError(
f'Missing required attribute "{name}" on codemod {codemod}'
)
for k, v in asdict(codemod.METADATA).items():
if v is NotImplemented:
raise NotImplementedError(f"METADATA.{k} not defined for {codemod}")
if k != "REFERENCES" and not v:
raise NotImplementedError(
f"METADATA.{k} should not be None or empty for {codemod}"
)
# TODO: eventually we will represent IS_SEMGREP using the class hierarchy
if codemod.is_semgrep and not codemod.YAML_FILES:
raise ValueError(
f"Missing required attribute YAML_FILES on semgrep codemod {codemod}"
)
def match_codemods(
self,
codemod_include: Optional[list] = None,
codemod_exclude: Optional[list] = None,
) -> list[CodemodExecutorWrapper]:
if not codemod_include and not codemod_exclude:
return self.codemods
codemod_include = codemod_include or []
codemod_exclude = codemod_exclude or []
# cli should've already prevented both include/exclude from being set.
assert codemod_include or codemod_exclude
if codemod_exclude:
return [
codemod
for codemod in self.codemods
if codemod.name not in codemod_exclude
and codemod.id not in codemod_exclude
]
return [
self._codemods_by_name.get(name) or self._codemods_by_id[name]
for name in codemod_include
]
def load_registered_codemods() -> CodemodRegistry:
registry = CodemodRegistry()
logger.debug("loading registered codemod collections")
for entry_point in entry_points().select(group="codemods"):
logger.debug(
'- loading codemod collection "%s" from "%s"',
entry_point.name,
entry_point.module,
)
collection = entry_point.load()
registry.add_codemod_collection(collection)
return registry