Skip to content
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

Port first batch of JS changes #316

Merged
merged 12 commits into from
Oct 5, 2023
2 changes: 2 additions & 0 deletions languages/tree-sitter-stack-graphs-javascript/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ cli = ["anyhow", "clap", "tree-sitter-stack-graphs/cli"]
[dependencies]
anyhow = { version = "1.0", optional = true }
clap = { version = "4", optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
stack-graphs = { version = "0.12", path = "../../stack-graphs" }
tree-sitter = "=0.20.9"
tree-sitter-stack-graphs = { version = "0.7", path = "../../tree-sitter-stack-graphs" }
Expand Down
8 changes: 7 additions & 1 deletion languages/tree-sitter-stack-graphs-javascript/rust/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ use tree_sitter_stack_graphs::loader::LanguageConfiguration;
use tree_sitter_stack_graphs::loader::LoadError;
use tree_sitter_stack_graphs::CancellationFlag;

use crate::npm_package::NpmPackageAnalyzer;

mod npm_package;
mod util;

/// The stack graphs tsg source for this language.
pub const STACK_GRAPHS_TSG_PATH: &str = "src/stack-graphs.tsg";
/// The stack graphs tsg source for this language.
Expand All @@ -24,6 +29,7 @@ pub const STACK_GRAPHS_BUILTINS_SOURCE: &str = include_str!("../src/builtins.js"

/// The name of the file path global variable.
pub const FILE_PATH_VAR: &str = "FILE_PATH";
pub const PROJECT_NAME_VAR: &str = "PROJECT_NAME";

pub fn language_configuration(cancellation_flag: &dyn CancellationFlag) -> LanguageConfiguration {
try_language_configuration(cancellation_flag).unwrap_or_else(|err| panic!("{}", err))
Expand All @@ -44,7 +50,7 @@ pub fn try_language_configuration(
STACK_GRAPHS_BUILTINS_SOURCE,
)),
Some(STACK_GRAPHS_BUILTINS_CONFIG),
FileAnalyzers::new(),
FileAnalyzers::new().add("package.json".to_string(), NpmPackageAnalyzer {}),
cancellation_flag,
)
}
172 changes: 172 additions & 0 deletions languages/tree-sitter-stack-graphs-javascript/rust/npm_package.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// -*- coding: utf-8 -*-
// ------------------------------------------------------------------------------------------------
// Copyright © 2022, stack-graphs authors.
// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
// ------------------------------------------------------------------------------------------------

use serde::Deserialize;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;

use stack_graphs::arena::Handle;
use stack_graphs::graph::File;
use stack_graphs::graph::StackGraph;
use tree_sitter_stack_graphs::BuildError;
use tree_sitter_stack_graphs::FileAnalyzer;

use crate::util::*;

pub struct NpmPackageAnalyzer {}

impl FileAnalyzer for NpmPackageAnalyzer {
fn build_stack_graph_into<'a>(
&self,
graph: &mut StackGraph,
file: Handle<File>,
_path: &Path,
source: &str,
_all_paths: &mut dyn Iterator<Item = &'a Path>,
globals: &HashMap<String, String>,
_cancellation_flag: &dyn tree_sitter_stack_graphs::CancellationFlag,
) -> Result<(), tree_sitter_stack_graphs::BuildError> {
// read globals
let pkg_internal_name = globals
.get(crate::PROJECT_NAME_VAR)
.map(String::as_str)
.unwrap_or_default();

// parse source
let npm_pkg: NpmPackage =
serde_json::from_str(source).map_err(|_| BuildError::ParseError)?;

let root = StackGraph::root_node();

// reach package internals from root
//
// [root] -> [pop "GUARD:PKG_INTERNAL"] -> [pop pkg_internal_name]
//
let pkg_internal_guard_pop = add_pop(
graph,
file,
root,
PKG_INTERNAL_GUARD,
"pkg_internal_guard_pop",
);
let pkg_internal_name_pop = add_pop(
graph,
file,
pkg_internal_guard_pop,
pkg_internal_name,
"pkg_internal_name_pop",
);

// reach package internals via root
//
// [push pkg_internal_name] -> [push "GUARD:PKG_INTERNAL"] -> [root]
//
let pkg_internal_guard_push = add_push(
graph,
file,
root,
PKG_INTERNAL_GUARD,
"pkg_internal_guard_push",
);
let pkg_internal_name_push = add_push(
graph,
file,
pkg_internal_guard_push,
pkg_internal_name,
"pkg_internal_name_push",
);

// reach exports via package name
//
// [root] -> [pop "GUARD:PKG"] -> [pop PKG_NAME]* -> [push PKG_INTERNAL_NAME] -> [push "GUARD:PKG_INTERNAL"] -> [root]
//
if !npm_pkg.name.is_empty() {
// reach package internals via package name
//
// [root] -> [pop "GUARD:PKG"] -> [pop pkg_name]* -> [push pkg_internal_name]
//
let pkg_guard_pop = add_pop(graph, file, root, PKG_GUARD, "pkg_guard_pop");
let pkg_name_pop = add_module_pops(
graph,
file,
Path::new(&npm_pkg.name),
pkg_guard_pop,
"pkg_name_pop",
);
add_edge(graph, pkg_name_pop, pkg_internal_name_push, 0);

// reach main exports directly via package name (with precedence)
//
// [pop pkg_name] -1-> [pop "GUARD:EXPORTS"] -> [push "GUARD:EXPORTS"] -> [push main]* -> [push pkg_internal_name]
//
let exports_guard_pop = add_pop(
graph,
file,
pkg_name_pop,
EXPORTS_GUARD,
"exports_guard_pop",
);
replace_edge(graph, pkg_name_pop, exports_guard_pop, 1);
let main = NormalizedRelativePath::from_str(&npm_pkg.main)
.map(|p| p.into_path_buf())
.unwrap_or(PathBuf::from("index"))
.with_extension("");
let main_push =
add_module_pushes(graph, file, &main, pkg_internal_name_push, "main_push");
let exports_guard_push =
add_push(graph, file, main_push, EXPORTS_GUARD, "exports_guard_push");
add_edge(graph, exports_guard_pop, exports_guard_push, 0);
}

// reach dependencies via package internal name
//
// [pop pkg_internal_name] -> [pop "GUARD:PKG"] -> [pop dep_name]* -> [push dep_name]* -> [push "GUARD:PKG"] -> [root]
//
let dep_guard_pop = add_pop(
graph,
file,
pkg_internal_name_pop,
PKG_GUARD,
"dep_guard_pop",
);
let dep_guard_push = add_push(graph, file, root, PKG_GUARD, "dep_guard_push");
for (i, (dep_name, _)) in npm_pkg.dependencies.iter().enumerate() {
if dep_name.is_empty() {
continue;
}
let dep_name_pop = add_module_pops(
graph,
file,
Path::new(dep_name),
dep_guard_pop,
&format!("dep_name_pop[{}]", i),
);
let dep_name_push = add_module_pushes(
graph,
file,
Path::new(dep_name),
dep_guard_push,
&format!("dep_name_push[{}", i),
);
add_edge(graph, dep_name_pop, dep_name_push, 0);
}

Ok(())
}
}

#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NpmPackage {
#[serde(default)]
pub name: String,
#[serde(default)]
pub main: String,
#[serde(default)]
pub dependencies: HashMap<String, serde_json::Value>,
}
184 changes: 184 additions & 0 deletions languages/tree-sitter-stack-graphs-javascript/rust/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// -*- coding: utf-8 -*-
// ------------------------------------------------------------------------------------------------
// Copyright © 2022, stack-graphs authors.
// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
// ------------------------------------------------------------------------------------------------

use stack_graphs::graph::Node;
use std::path::Component;
use std::path::Path;
use std::path::PathBuf;

use stack_graphs::arena::Handle;
use stack_graphs::graph::File;
use stack_graphs::graph::StackGraph;

pub const EXPORTS_GUARD: &str = "GUARD:EXPORTS";
pub const PKG_GUARD: &str = "GUARD:PKG";
pub const PKG_INTERNAL_GUARD: &str = "GUARD:PKG_INTERNAL";

pub fn add_debug_name(graph: &mut StackGraph, node: Handle<Node>, name: &str) {
let key = graph.add_string("name");
let value = graph.add_string(name);
graph.node_debug_info_mut(node).add(key, value);
}

pub fn add_pop(
graph: &mut StackGraph,
file: Handle<File>,
from: Handle<Node>,
name: &str,
debug_name: &str,
) -> Handle<Node> {
let id = graph.new_node_id(file);
let sym = graph.add_symbol(name);
let node = graph.add_pop_symbol_node(id, sym, false).unwrap();
graph.add_edge(from, node, 0);
add_debug_name(graph, node, debug_name);
node
}

pub fn add_push(
graph: &mut StackGraph,
file: Handle<File>,
to: Handle<Node>,
name: &str,
debug_name: &str,
) -> Handle<Node> {
let id = graph.new_node_id(file);
let sym = graph.add_symbol(name);
let node = graph.add_push_symbol_node(id, sym, false).unwrap();
graph.add_edge(node, to, 0);
add_debug_name(graph, node, debug_name);
node
}

pub fn add_edge(graph: &mut StackGraph, from: Handle<Node>, to: Handle<Node>, precedence: i32) {
if from == to {
return;
}
graph.add_edge(from, to, precedence);
}

pub fn replace_edge(graph: &mut StackGraph, from: Handle<Node>, to: Handle<Node>, precedence: i32) {
if from == to {
return;
}
graph.remove_edge(from, to);
graph.add_edge(from, to, precedence);
}

pub fn add_module_pops(
graph: &mut StackGraph,
file: Handle<File>,
path: &Path,
mut from: Handle<Node>,
debug_prefix: &str,
) -> Handle<Node> {
for (i, c) in path.components().enumerate() {
match c {
Component::Normal(name) => {
from = add_pop(
graph,
file,
from,
&name.to_string_lossy(),
&format!("{}[{}]", debug_prefix, i),
);
}
_ => {
eprintln!(
"add_module_pops: expecting normalized, non-escaping, relative paths, got {}",
path.display()
)
}
}
}
from
}

pub fn add_module_pushes(
graph: &mut StackGraph,
file: Handle<File>,
path: &Path,
mut to: Handle<Node>,
debug_prefix: &str,
) -> Handle<Node> {
for (i, c) in path.components().enumerate() {
match c {
Component::Normal(name) => {
to = add_push(
graph,
file,
to,
&name.to_string_lossy(),
&format!("{}[{}]", debug_prefix, i),
);
}
_ => {
eprintln!(
"add_module_pushes: expecting normalized, non-escaping, relative paths, got {}",
path.display()
)
}
}
}
to
}

pub struct NormalizedRelativePath(PathBuf);

impl NormalizedRelativePath {
pub fn from_str(path: &str) -> Option<Self> {
Self::from_path(Path::new(path))
}

/// Creates a new normalized, relative path from a path.
pub fn from_path(path: &Path) -> Option<Self> {
let mut np = PathBuf::new();
let mut normal_components = 0usize;
for c in path.components() {
match c {
Component::Prefix(_) => {
return None;
}
Component::RootDir => {
return None;
}
Component::CurDir => {}
Component::ParentDir => {
if normal_components > 0 {
// we can pop a normal component
normal_components -= 1;
np.pop();
} else {
// add the `..` to the beginning of this relative path which has no normal components
np.push(c);
}
}
Component::Normal(_) => {
normal_components += 1;
np.push(c);
}
}
}
Some(Self(np))
}

pub fn into_path_buf(self) -> PathBuf {
self.0
}
}

impl AsRef<Path> for NormalizedRelativePath {
fn as_ref(&self) -> &Path {
&self.0
}
}

impl Into<PathBuf> for NormalizedRelativePath {
fn into(self) -> PathBuf {
self.0
}
}
Loading