Skip to content

Instantly share code, notes, and snippets.

@sylann
Last active April 6, 2021 12:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sylann/d698993edd63d4b24f6b14c2685f568d to your computer and use it in GitHub Desktop.
Save sylann/d698993edd63d4b24f6b14c2685f568d to your computer and use it in GitHub Desktop.
Find all missing imports of components in a javascript es6 project
"""Find missing imports
Suitable for javascript projects using es6 imports.
Example uses:
Assuming SCRIPT is <path/to/find_missing_es6_imports.py>
First argument is a glob pattern to find components
Following arguments are paths to files or folders relative to current directory
- $ python3.9 SCRIPT '**/*.vue' components
- $ python3.9 SCRIPT '**/*.vue' components layouts pages
Note:
The script requires python3.9
For python >=3.7,<3.9, add this import: `from __future__ import annotations`
For python >3,<3.7, remove typing annotations and replace f-strings with "".format()
"""
import pathlib
import re
import subprocess
from typing import Generator, Iterable, Iterator
def iter_component_uses(globs: Iterable[str]) -> Generator[tuple[str, str], None, None]:
"""Find occurences of used components throughout all files nested
in the given globs.
For each occurence, yield a tuple of (file_path, component_name)
where file_path is the relative path to a file using a component
and component_name is the name of the component used in this file
"""
p = subprocess.run(
("grep", "-roE", "<[A-Z]\w+", *globs),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
p.check_returncode()
for line in p.stdout.decode("utf-8").split("\n"):
if not line.strip(): # skip empty lines
continue
if ":<" not in line: # Report malformed lines
print("Unexpected line:", line)
continue
yield line.split(":<")
class ComponentFile:
def __init__(self, path: str):
self.path = pathlib.Path(path)
self.content = self.path.read_text()
self.basename = self.path.name.replace(self.path.suffix, "")
# Storage of used components to check for missing imports
self.components_to_check: set[str] = set()
def find_component_use_occurences(iterator: Iterator[tuple[str, str]]) -> dict[str, ComponentFile]:
"""Store occurences of source components grouped by the name of the source file
that uses them.
"""
grouping = {}
for file_path, component_name in iterator:
if file_path not in grouping:
grouping[file_path] = ComponentFile(file_path)
grouping[file_path].components_to_check.add(component_name)
return grouping
def main(component_glob: str, sources: list[str]):
files = find_component_use_occurences(iterator=iter_component_uses(sources))
# Iterate over source files nested in the components folder to check if they
# are used but not imported.
for component_path in pathlib.Path("components").glob(component_glob):
component_name = component_path.with_suffix("").name
for vf in files.values():
if component_name not in vf.components_to_check:
continue
vf.components_to_check.remove(component_name)
is_not_imported = re.search(
fr"^\s*import {component_name}",
vf.content,
re.MULTILINE,
) is None
if is_not_imported:
print(f"{vf.path}: Missing import for {component_name}")
if __name__ == "__main__":
import argparse
p = argparse.ArgumentParser()
p.add_argument("component_glob", type=str)
p.add_argument("sources_globs", type=pathlib.Path, nargs="*")
args = p.parse_args()
main(args.component_glob, args.sources_globs)
@sylann
Copy link
Author

sylann commented Apr 6, 2021

Note: Recursive components will be listed too. And possibly other problems that I did not encounter yet.

But this changes the problem from "finding something that might not exist" (PITA) to "simply check what is listed".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment