1616 import tomli as tomllib
1717
1818from natrix .__version__ import __version__
19- from natrix .ast_tools import parse_file
2019from natrix .codegen import generate_exports
20+ from natrix .context import ProjectContext
2121from natrix .rules .common import Issue , RuleRegistry
2222
23+ # Vyper file extensions
24+ VYPER_EXTENSIONS = (".vy" , ".vyi" )
25+
2326
2427class OutputFormatter :
2528 """Handles output formatting for both CLI and JSON modes."""
@@ -67,14 +70,12 @@ def print_summary(self, has_issues: bool) -> None:
6770
6871
6972def lint_file (
70- file_path : str ,
73+ file_path : Path ,
74+ project_context : ProjectContext ,
7175 formatter : OutputFormatter ,
7276 disabled_rules : set [str ] | None = None ,
73- extra_paths : tuple [str , ...] = (),
7477) -> list [Issue ]:
75- """Lint a single Vyper file with the given rules configuration."""
76- ast = parse_file (file_path , extra_paths = extra_paths )
77-
78+ """Lint a single Vyper file using the pre-built project context."""
7879 if disabled_rules is None :
7980 disabled_rules = set ()
8081
@@ -85,7 +86,7 @@ def lint_file(
8586 issues = []
8687 for rule in rules :
8788 try :
88- rule_issues = rule .run (ast )
89+ rule_issues = rule .run (project_context , file_path )
8990 issues .extend (
9091 [issue for issue in rule_issues if issue .code not in disabled_rules ]
9192 )
@@ -99,30 +100,31 @@ def lint_file(
99100 return issues
100101
101102
102- def find_vy_files (directory : str ) -> list [str ]:
103- # Recursively find all .vy files in the given directory,
103+ def find_vy_files (directory : Path ) -> list [Path ]:
104+ # Recursively find all Vyper files ( .vy and .vyi) in the given directory,
104105 # excluding specified directories
105106 vy_files = []
106107 for root , _ , files in os .walk (directory ):
107- # Collect all .vy files
108+ # Collect all Vyper files
108109 for file in files :
109- if file .endswith (".vy" ):
110- vy_files .append (str (Path (root ) / file ))
110+ file_path = Path (root ) / file
111+ if file_path .suffix in VYPER_EXTENSIONS :
112+ vy_files .append (file_path )
111113
112114 return vy_files
113115
114116
115- def get_project_root () -> str :
117+ def get_project_root () -> Path :
116118 """Get the project root directory, which contains the pyproject.toml file."""
117119 # First try to find it from the current working directory upwards
118120 current_path = Path .cwd ().resolve ()
119121
120122 for parent in [current_path , * current_path .parents ]:
121123 if (parent / "pyproject.toml" ).exists ():
122- return str ( parent )
124+ return parent
123125
124126 # If not found, default to the current directory
125- return str ( Path .cwd () )
127+ return Path .cwd ()
126128
127129
128130def read_pyproject_config () -> dict [str , Any ]:
@@ -137,7 +139,7 @@ def read_pyproject_config() -> dict[str, Any]:
137139 try :
138140 # Find the project root directory
139141 project_root = get_project_root ()
140- pyproject_path = Path ( project_root ) / "pyproject.toml"
142+ pyproject_path = project_root / "pyproject.toml"
141143
142144 if pyproject_path .exists ():
143145 with pyproject_path .open ("rb" ) as f :
@@ -149,7 +151,7 @@ def read_pyproject_config() -> dict[str, Any]:
149151 ):
150152 # Make paths relative to pyproject.toml location
151153 config ["files" ] = [
152- str (( Path ( project_root ) / path ).resolve () )
154+ ( project_root / path ).resolve ()
153155 for path in natrix_config ["files" ]
154156 ]
155157 if "disabled_rules" in natrix_config and isinstance (
@@ -167,7 +169,7 @@ def read_pyproject_config() -> dict[str, Any]:
167169 ):
168170 # Make paths relative to pyproject.toml location
169171 config ["path" ] = [
170- str (( Path ( project_root ) / path ).resolve () )
172+ ( project_root / path ).resolve ()
171173 for path in natrix_config ["path" ]
172174 ]
173175 except Exception as e :
@@ -280,9 +282,9 @@ def main() -> None:
280282 if args .command == "codegen" :
281283 if args .codegen_command == "exports" :
282284 # Get extra paths if provided
283- extra_paths = tuple (args .path ) if args .path else ()
285+ extra_paths = tuple (Path ( p ) for p in args .path ) if args .path else ()
284286 # Generate and print exports
285- exports = generate_exports (args .file_path , extra_paths )
287+ exports = generate_exports (Path ( args .file_path ) , extra_paths )
286288 print (exports )
287289 sys .exit (0 )
288290 else :
@@ -361,36 +363,43 @@ def main() -> None:
361363 if args .files :
362364 all_vy_files = []
363365 for path in args .files :
364- path_obj = Path (path )
365- if path_obj .is_file () and path .endswith ( ".vy" ) :
366+ path = Path (path )
367+ if path .is_file () and path .suffix in VYPER_EXTENSIONS :
366368 all_vy_files .append (path )
367- elif path_obj .is_dir ():
369+ elif path .is_dir ():
368370 dir_vy_files = find_vy_files (path )
369371 if not dir_vy_files :
370- formatter .print (f"No .vy files found in the directory: { path } " )
372+ formatter .print (f"No Vyper files found in the directory: { path } " )
371373 all_vy_files .extend (dir_vy_files )
372374 else :
373375 formatter .print (
374- f"Provided path is not a valid .vy file or directory: { path } "
376+ f"Provided path is not a valid Vyper file or directory: { path } "
375377 )
376378
377379 if not all_vy_files :
378- formatter .print ("No valid .vy files to lint." )
380+ formatter .print ("No valid Vyper files to lint." )
379381 sys .exit (1 )
380382 else :
381- # If no paths are provided, search for .vy files in the current
383+ # If no paths are provided, search for Vyper files in the current
382384 # directory recursively
383- all_vy_files = find_vy_files ("." )
385+ all_vy_files = find_vy_files (Path () )
384386
385387 if not all_vy_files :
386- formatter .print ("No .vy files found in the current directory." )
388+ formatter .print ("No Vyper files found in the current directory." )
387389 sys .exit (1 )
388390
391+ # Create ProjectContext with all files
392+ formatter .print ("Building project dependency graph..." )
393+ project_context = ProjectContext (
394+ all_vy_files ,
395+ extra_paths = tuple (Path (p ) if isinstance (p , str ) else p for p in extra_paths ),
396+ )
397+
389398 # Collect all issues from all files
390399 all_issues : list [Issue ] = []
391400 for file in all_vy_files :
392401 file_issues = lint_file (
393- file , formatter , disabled_rules , extra_paths = extra_paths
402+ Path ( file ). resolve (), project_context , formatter , disabled_rules
394403 )
395404 all_issues .extend (file_issues )
396405
0 commit comments