-
-
Notifications
You must be signed in to change notification settings - Fork 10.6k
feat(extract): recognize .zsh and add SAS (.sas) extraction #2835
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
SomSamantray
wants to merge
6
commits into
Graphify-Labs:v8
Choose a base branch
from
SomSamantray:feat/zsh-sas-extraction
base: v8
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+372
−9
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
ec74a42
feat(extract): recognize .zsh files through the shell extractor (#2825)
SomSamantray 15fd50a
feat(extract): add SAS (.sas) structural extraction (#2681)
SomSamantray df4b3c1
docs(readme): list .zsh and .sas in supported file types
SomSamantray e5e875f
refactor(extract): reuse _read_text, tag .zsh shell family, disambigu…
SomSamantray 9de8cd4
fix(extract): harden SAS macro resolution and zsh source edges
SomSamantray 8a47e83
chore: trigger re-review
SomSamantray File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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,131 @@ | ||
| """SAS extractor (tree-sitter). | ||
|
|
||
| Extracts data steps, proc steps, and %macro definitions from a .sas file, | ||
| plus calls edges from %macro call sites to macros defined in the same file. | ||
| """ | ||
| from __future__ import annotations | ||
|
|
||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| from graphify.extractors.base import _file_stem, _make_id, _read_text | ||
|
|
||
|
|
||
| def extract_sas(path: Path) -> dict: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
fans out to 8 callees (efferent coupling); 12 callers depend on it (afferent coupling). Grounded coupling-delta finding (deterministic), not an LLM guess. |
||
| """Extract data/proc steps and macro definitions from a .sas file.""" | ||
| try: | ||
| import tree_sitter_sas as tssas | ||
| from tree_sitter import Language, Parser | ||
| except ImportError as e: | ||
| import importlib.util | ||
| # Distinguish a genuinely-absent grammar from an installed-but-broken | ||
| # one (e.g. a C extension built for a different Python ABI, #2602) so | ||
| # the #1745 warning does not send the user to a no-op install. | ||
| if importlib.util.find_spec("tree_sitter_sas") is None: | ||
| return {"nodes": [], "edges": [], | ||
| "error": "tree_sitter_sas not installed"} | ||
| return {"nodes": [], "edges": [], | ||
| "error": f"tree_sitter_sas is installed but failed to load: {e}"} | ||
|
|
||
| try: | ||
| language = Language(tssas.language()) | ||
| parser = Parser(language) | ||
| source = path.read_bytes() | ||
| tree = parser.parse(source) | ||
| root = tree.root_node | ||
| except Exception as e: | ||
| return {"nodes": [], "edges": [], "error": str(e)} | ||
|
|
||
| stem = _file_stem(path) | ||
| str_path = str(path) | ||
| nodes: list[dict] = [] | ||
| edges: list[dict] = [] | ||
| seen_ids: set[str] = set() | ||
| seen_edges: set[tuple[str, str, str]] = set() | ||
| macro_defs: dict[str, str] = {} | ||
|
|
||
| def add_node(nid: str, label: str, line: int) -> None: | ||
| if nid not in seen_ids: | ||
| seen_ids.add(nid) | ||
| nodes.append({"id": nid, "label": label, "file_type": "code", | ||
| "source_file": str_path, "source_location": f"L{line}"}) | ||
|
|
||
| def add_edge(src: str, tgt: str, relation: str, line: int, | ||
| context: str | None = None) -> None: | ||
| key = (src, tgt, relation) | ||
| if key in seen_edges: | ||
| return | ||
| seen_edges.add(key) | ||
| edge = {"source": src, "target": tgt, "relation": relation, | ||
| "confidence": "EXTRACTED", "source_file": str_path, | ||
| "source_location": f"L{line}", "weight": 1.0} | ||
| if context: | ||
| edge["context"] = context | ||
| edges.append(edge) | ||
|
|
||
| file_nid = _make_id(str(path)) | ||
| add_node(file_nid, path.name, 1) | ||
|
|
||
| def _child_text(node: Any, child_types: tuple[str, ...]) -> str | None: | ||
| for child in node.children: | ||
| if child.type in child_types: | ||
| return _read_text(child, source).strip() | ||
| return None | ||
|
|
||
| def _macro_name_text(node: Any) -> str | None: | ||
| return _child_text(node, ("macro_name",)) | ||
|
|
||
| # First pass: collect macro definitions (case-insensitively, per SAS) so | ||
| # call sites resolve regardless of where the definition appears. | ||
| for node in root.children: | ||
| if node.type == "macro_definition": | ||
| name = _macro_name_text(node) | ||
| if name: | ||
| macro_defs[name.casefold()] = _make_id(stem, name) | ||
|
|
||
| def _step_label(node: Any) -> str | None: | ||
| text = _child_text(node, ("data_step_header", "proc_step_header")) | ||
| # strip the trailing `;` so the label reads `data work.customers` | ||
| return text.rstrip(";").strip() if text else None | ||
|
|
||
| def _emit_macro_calls(node: Any) -> None: | ||
| """Emit calls edges for macro call statements anywhere in the subtree.""" | ||
| stack = [node] | ||
| while stack: | ||
| current = stack.pop() | ||
| if current.type == "macro_call_statement": | ||
| name = _macro_name_text(current) | ||
| if name: | ||
| nid = macro_defs.get(name.casefold()) | ||
| if nid: | ||
| add_edge(file_nid, nid, "calls", | ||
| current.start_point.row + 1, context="call") | ||
| stack.extend(current.children) | ||
|
|
||
| for node in root.children: | ||
| if node.type == "macro_definition": | ||
| name = _macro_name_text(node) | ||
| if not name: | ||
| continue | ||
| nid = macro_defs[name.casefold()] | ||
| add_node(nid, f"%{name}", node.start_point.row + 1) | ||
| add_edge(file_nid, nid, "defines", node.start_point.row + 1, context="macro") | ||
| _emit_macro_calls(node) | ||
| elif node.type == "data_step": | ||
| label = _step_label(node) or "data" | ||
| # disambiguate by byte offset so multiple steps (even on one line) | ||
| # stay distinct | ||
| nid = _make_id(stem, "data", str(node.start_byte)) | ||
| add_node(nid, label, node.start_point.row + 1) | ||
| add_edge(file_nid, nid, "defines", node.start_point.row + 1, context="data_step") | ||
| _emit_macro_calls(node) | ||
| elif node.type == "proc_step": | ||
| label = _step_label(node) or "proc" | ||
| nid = _make_id(stem, "proc", str(node.start_byte)) | ||
| add_node(nid, label, node.start_point.row + 1) | ||
| add_edge(file_nid, nid, "defines", node.start_point.row + 1, context="proc_step") | ||
| _emit_macro_calls(node) | ||
| else: | ||
| _emit_macro_calls(node) | ||
|
|
||
| return {"nodes": nodes, "edges": edges} | ||
This file contains hidden or 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
This file contains hidden or 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,14 @@ | ||
| %macro greet(name); | ||
| %put Hello &name; | ||
| %mend greet; | ||
|
|
||
| data work.customers; | ||
| set raw.import; | ||
| length name $ 50; | ||
| run; | ||
|
|
||
| proc sort data=work.customers; | ||
| by name; | ||
| run; | ||
|
|
||
| %greet(World); |
This file contains hidden or 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,14 @@ | ||
| #!/usr/bin/env zsh | ||
| set -euo pipefail | ||
|
|
||
| greet() { | ||
| print "Hello, $1" | ||
| } | ||
|
|
||
| deploy() { | ||
| local env_name="${1:-production}" | ||
| greet "$env_name" | ||
| print "Deploying to $env_name" | ||
| } | ||
|
|
||
| deploy staging |
This file contains hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.