Skip to content

Commit

Permalink
Support for TSX
Browse files Browse the repository at this point in the history
Preprocesses the .tsg file into typescript and tsx specific
.tsg files. The preprocessing is done in the build script.

Add a --dialect|-d option to the CLI to select the dialect.
  • Loading branch information
eyakubovich committed Mar 10, 2024
1 parent 88bc170 commit cd323fd
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 61 deletions.
6 changes: 4 additions & 2 deletions languages/tree-sitter-stack-graphs-typescript/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ lsp = ["tree-sitter-stack-graphs/lsp"]

[dependencies]
anyhow = { version = "1.0", optional = true }
askama = "0.12.1"
clap = { version = "4", optional = true }
glob = "0.3"
lazy_static = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
stack-graphs = { version = "0.13", path = "../../stack-graphs" }
Expand All @@ -42,3 +40,7 @@ tsconfig = "0.1.0"
[dev-dependencies]
anyhow = { version = "1.0" }
tree-sitter-stack-graphs = { version = "0.8", path = "../../tree-sitter-stack-graphs", features = ["cli"] }

[build-dependencies]
anyhow = { version = "1.0" }
regex = "1.10.3"
9 changes: 0 additions & 9 deletions languages/tree-sitter-stack-graphs-typescript/askama.toml

This file was deleted.

98 changes: 98 additions & 0 deletions languages/tree-sitter-stack-graphs-typescript/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// -*- coding: utf-8 -*-
// ------------------------------------------------------------------------------------------------
// Copyright © 2024, 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 std::collections::HashSet;
use std::path::Path;

use anyhow::{Result, anyhow, bail};
use regex::Regex;

/// A stack of dialects as selected by the directives
#[derive(Debug, Default)]
struct DialectStack(Vec<HashSet<String>>);

impl DialectStack {
fn push(&mut self, values: Vec<String>) -> Result<()> {
// ensure that the new values are a subset of the current scope
if let Some(current) = self.0.last() {
if !values.iter().all(|v| current.contains(v)) {
bail!("Directive values are not a subset of the current scope");
}
}

self.0.push(values.into_iter().collect());

Ok(())
}

fn pop(&mut self) -> Result<()> {
if let Some(_) = self.0.pop() {
Ok(())
} else {
Err(anyhow!("Directive stack is empty"))
}
}

fn contains(&self, query: &str) -> bool {
if let Some(current) = self.0.last() {
current.contains(query)
} else {
true
}
}
}

/// preprocess the input file, removing lines that are not for the selected dialect
fn preprocess(input: impl std::io::Read, mut output: impl std::io::Write, dialect: &str) -> anyhow::Result<()> {
// Matches: ; # dialect typescript tsx
let directive_start = Regex::new(r";[ \t]*#[ \t]*dialect[ \t]+([a-zA-Z\t ]+)").unwrap();

// Matches: ; # end
let directirve_end = Regex::new(r";[ \t]*#[ \t]*end").unwrap();

let input = std::io::read_to_string(input)?;

let mut stack = DialectStack::default();

for line in input.lines() {
if let Some(captures) = directive_start.captures(line) {
let directive = captures.get(1).unwrap().as_str();
let dialects = directive.split_whitespace().map(|s| s.to_string()).collect();
stack.push(dialects)?;
output.write_all(line.as_bytes())?;
} else if directirve_end.is_match(line) {
stack.pop()?;
output.write_all(line.as_bytes())?;
} else {
if stack.contains(dialect) {
output.write_all(line.as_bytes())?;
}
}
// a new line is always written so that removed lines are padded to preserve line numbers
output.write(b"\n")?;
}

Ok(())
}

const TSG_SOURCE: &str = "src/stack-graphs.tsg";
const DIALECTS: [&str; 2] = ["typescript", "tsx"];

fn main() {
let out_dir = std::env::var_os("OUT_DIR").unwrap();
for dialect in DIALECTS {
let input = std::fs::File::open(TSG_SOURCE).unwrap();

let out_filename = Path::new(&out_dir).join(format!("stack-graphs-{dialect}.tsg"));
let output = std::fs::File::create(out_filename).unwrap();

preprocess(input, output, dialect).unwrap();
}

println!("cargo:rerun-if-changed={TSG_SOURCE}");
println!("cargo:rerun-if-changed=build.rs");
}
21 changes: 18 additions & 3 deletions languages/tree-sitter-stack-graphs-typescript/rust/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,37 @@ use tree_sitter_stack_graphs::cli::provided_languages::Subcommands;
use tree_sitter_stack_graphs::NoCancellation;

fn main() -> anyhow::Result<()> {
let lc = match tree_sitter_stack_graphs_typescript::try_language_configuration(&NoCancellation)
let cli = Cli::parse();
let lc = match language_configuration(&cli.dialect)
{
Ok(lc) => lc,
Err(err) => {
eprintln!("{}", err.display_pretty());
eprintln!("{err}");
return Err(anyhow!("Language configuration error"));
}
};
let cli = Cli::parse();
let default_db_path = default_user_database_path_for_crate(env!("CARGO_PKG_NAME"))?;
cli.subcommand.run(default_db_path, vec![lc])
}

fn language_configuration(dialect: &str) -> anyhow::Result<tree_sitter_stack_graphs::loader::LanguageConfiguration> {
match dialect {
"typescript" => tree_sitter_stack_graphs_typescript::try_language_configuration(&NoCancellation)
.map_err(|e| anyhow::anyhow!("{}", e.display_pretty())),

"tsx" => tree_sitter_stack_graphs_typescript::try_language_configuration_tsx(&NoCancellation)
.map_err(|e| anyhow::anyhow!("{}", e.display_pretty())),

_ => anyhow::bail!("Unknown dialect: {}", dialect)
}
}

#[derive(Parser)]
#[clap(about, version)]
pub struct Cli {
#[clap(short, long, default_value = "typescript")]
dialect: String,

#[clap(subcommand)]
subcommand: Subcommands,
}
50 changes: 13 additions & 37 deletions languages/tree-sitter-stack-graphs-typescript/rust/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@
// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
// ------------------------------------------------------------------------------------------------

use std::path::PathBuf;
use std::borrow::Cow;

use askama::Template;
use lazy_static::lazy_static;
use tree_sitter_stack_graphs::loader::LanguageConfiguration;
use tree_sitter_stack_graphs::loader::LoadError;
use tree_sitter_stack_graphs::CancellationFlag;
Expand All @@ -22,9 +17,11 @@ pub mod tsconfig;
pub mod util;

/// The stacks graphs tsg path for this language.
pub const STACK_GRAPHS_TSG_PATH: &str = "src/stack-graphs.tsg";
pub const STACK_GRAPHS_TSG_TS_PATH: &str = concat!(env!("OUT_DIR"), "/stack-graphs-typescript.tsg");
pub const STACK_GRAPHS_TSG_TSX_PATH: &str = concat!(env!("OUT_DIR"), "/stack-graphs-tsx.tsg");
/// The stack graphs tsg source for this language
pub const STACK_GRAPHS_TSG_SOURCE: &str = include_str!("../src/stack-graphs.tsg");
pub const STACK_GRAPHS_TSG_TS_SOURCE: &str = include_str!(concat!(env!("OUT_DIR"), "/stack-graphs-typescript.tsg"));
pub const STACK_GRAPHS_TSG_TSX_SOURCE: &str = include_str!(concat!(env!("OUT_DIR"), "/stack-graphs-tsx.tsg"));

/// The stack graphs builtins configuration for this language
pub const STACK_GRAPHS_BUILTINS_CONFIG: &str = include_str!("../src/builtins.cfg");
Expand All @@ -38,25 +35,10 @@ pub const FILE_PATH_VAR: &str = "FILE_PATH";
/// The name of the project name global variable
pub const PROJECT_NAME_VAR: &str = "PROJECT_NAME";

lazy_static! {
static ref STACK_GRAPHS_TS_TSG_SOURCE: String = preprocess_tsg("typescript").unwrap();
static ref STACK_GRAPHS_TSX_TSG_SOURCE: String = preprocess_tsg("tsx").unwrap();
}

#[derive(Template)]
#[template(path = "stack-graphs.tsg")]
struct TsgTemplate<'a> {
dialect: &'a str,
}

pub fn language_configuration(cancellation_flag: &dyn CancellationFlag) -> LanguageConfiguration {
try_language_configuration(cancellation_flag).unwrap_or_else(|err| panic!("{}", err))
}

pub fn tsx_language_configuration(cancellation_flag: &dyn CancellationFlag) -> LanguageConfiguration {
try_tsx_language_configuration(cancellation_flag).unwrap_or_else(|err| panic!("{}", err))
}

pub fn try_language_configuration(
cancellation_flag: &dyn CancellationFlag,
) -> Result<LanguageConfiguration, LoadError> {
Expand All @@ -65,8 +47,8 @@ pub fn try_language_configuration(
Some(String::from("source.ts")),
None,
vec![String::from("ts")],
STACK_GRAPHS_TSG_PATH.into(),
&STACK_GRAPHS_TS_TSG_SOURCE,
STACK_GRAPHS_TSG_TS_PATH.into(),
STACK_GRAPHS_TSG_TS_SOURCE,
Some((
STACK_GRAPHS_BUILTINS_PATH.into(),
STACK_GRAPHS_BUILTINS_SOURCE,
Expand All @@ -81,16 +63,20 @@ pub fn try_language_configuration(
Ok(lc)
}

pub fn try_tsx_language_configuration(
pub fn language_configuration_tsx(cancellation_flag: &dyn CancellationFlag) -> LanguageConfiguration {
try_language_configuration_tsx(cancellation_flag).unwrap_or_else(|err| panic!("{}", err))
}

pub fn try_language_configuration_tsx(
cancellation_flag: &dyn CancellationFlag,
) -> Result<LanguageConfiguration, LoadError> {
let mut lc = LanguageConfiguration::from_sources(
tree_sitter_typescript::language_tsx(),
Some(String::from("source.tsx")),
None,
vec![String::from("tsx")],
STACK_GRAPHS_TSG_PATH.into(),
&STACK_GRAPHS_TSX_TSG_SOURCE,
STACK_GRAPHS_TSG_TSX_PATH.into(),
STACK_GRAPHS_TSG_TSX_SOURCE,
Some((
STACK_GRAPHS_BUILTINS_PATH.into(),
STACK_GRAPHS_BUILTINS_SOURCE,
Expand All @@ -104,13 +90,3 @@ pub fn try_tsx_language_configuration(
lc.no_similar_paths_in_file = true;
Ok(lc)
}

fn preprocess_tsg(dialect: &'static str) -> Result<String, LoadError<'static>> {
let tmpl = TsgTemplate{ dialect };
tmpl.render()
.map_err(|err| LoadError::PreprocessorError {
inner: err.to_string(),
tsg_path: PathBuf::from(STACK_GRAPHS_TSG_PATH),
tsg: Cow::from(STACK_GRAPHS_TSG_SOURCE),
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -2665,9 +2665,9 @@ if none @is_async {
(ternary_expression)
(this)
(true)
{% if dialect == "typescript" -%}
; #dialect typescript
(type_assertion)
{% endif -%}
; #end
(unary_expression)
(undefined)
(update_expression)
Expand Down Expand Up @@ -4042,7 +4042,7 @@ if none @is_async {
}


{% if dialect == "typescript" -%}
; #dialect typescript
;; Type Assertion

; (type_assertion
Expand All @@ -4057,7 +4057,7 @@ if none @is_async {
; propagate lexical scope
edge @expr.lexical_scope -> @type_assert.lexical_scope
}
{% endif -%}
; #end


;; As Expression
Expand Down
6 changes: 0 additions & 6 deletions tree-sitter-stack-graphs/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,6 @@ pub enum LoadError<'a> {
},
#[error(transparent)]
TreeSitter(anyhow::Error),
#[error("Preprocessor error: {inner}")]
PreprocessorError {
inner: String,
tsg_path: PathBuf,
tsg: Cow<'a, str>,
}
}

impl LoadError<'_> {
Expand Down

0 comments on commit cd323fd

Please sign in to comment.