Skip to content

Commit

Permalink
feat: extend portable
Browse files Browse the repository at this point in the history
  • Loading branch information
wtlin1228 committed Oct 5, 2024
1 parent a0dea69 commit 4fc74dc
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 144 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,12 @@ let import_module_path = path_resolver.resolve_path("<current_module_path>", "<i
`Portable` defines the structure of the portable files.

```rs
let portable = Portable::new(i18n_usages, used_by_graph);
let portable = Portable::new(
project_root,
i18n_to_symbol,
symbol_to_route,
used_by_graph
);
let serialized = portable.export().unwrap();
let portable = Portable::import(serialized).unwrap();
```
Expand Down
10 changes: 5 additions & 5 deletions crates/api_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ use std::{

struct AppState {
project_root: String,
translation_usage: HashMap<String, HashMap<String, HashSet<String>>>,
i18n_to_symbol: HashMap<String, HashMap<String, HashSet<String>>>,
used_by_graph: UsedByGraph,
}

#[derive(Serialize)]
struct SearchResponse {
project_root: String,
translation_usage: HashMap<String, HashSet<String>>,
i18n_to_symbol: HashMap<String, HashSet<String>>,
trace_result: HashMap<String, HashMap<String, Vec<Vec<ModuleSymbol>>>>,
}

Expand All @@ -32,7 +32,7 @@ async fn search(
) -> Result<web::Json<SearchResponse>> {
let search = path.into_inner();

match data.translation_usage.get(&search) {
match data.i18n_to_symbol.get(&search) {
None => Err(error::ErrorNotFound(format!("{} not found", search))),
Some(ts) => {
let mut dependency_tracker = DependencyTracker::new(&data.used_by_graph, true);
Expand All @@ -54,7 +54,7 @@ async fn search(

Ok(web::Json(SearchResponse {
project_root: data.project_root.to_owned(),
translation_usage: ts.to_owned(),
i18n_to_symbol: ts.to_owned(),
trace_result,
}))
}
Expand All @@ -76,7 +76,7 @@ async fn main() -> std::io::Result<()> {
.wrap(Cors::default().allowed_origin("http://localhost:5173"))
.app_data(web::Data::new(AppState {
project_root: portable.project_root.clone(),
translation_usage: portable.translation_usage.clone(),
i18n_to_symbol: portable.i18n_to_symbol.clone(),
used_by_graph: portable.used_by_graph.clone(),
}))
.service(search)
Expand Down
36 changes: 20 additions & 16 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use anyhow::Context;
use clap::Parser;
use dt_core::{
graph::{depend_on_graph::DependOnGraph, used_by_graph::UsedByGraph},
i18n::collect_all_translation_usage,
i18n::I18nToSymbol,
parser::{collect_symbol_dependency, Input},
path_resolver::ToCanonicalString,
portable::Portable,
route::SymbolToRoutes,
scheduler::ParserCandidateScheduler,
};

Expand All @@ -27,33 +28,36 @@ fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let project_root = PathBuf::from(&cli.input).to_canonical_string()?;

let portable = Portable::new(
project_root.to_owned(),
collect_all_translation_usage(&project_root)?,
construct_used_by_graph(&project_root)?,
);

let serialized = portable.export()?;
let mut file = File::create(&cli.output)?;
file.write_all(serialized.as_bytes())?;

Ok(())
}

fn construct_used_by_graph(project_root: &str) -> anyhow::Result<UsedByGraph> {
let mut scheduler = ParserCandidateScheduler::new(&project_root);
let mut depend_on_graph = DependOnGraph::new(&project_root);
let mut symbol_to_route = SymbolToRoutes::new();
let mut i18n_to_symbol = I18nToSymbol::new();
loop {
match scheduler.get_one_candidate() {
Some(c) => {
let module_src = c.to_str().context(format!("to_str() failed: {:?}", c))?;
let module_ast = Input::Path(module_src).get_module_ast()?;
let symbol_dependency = collect_symbol_dependency(&module_ast, module_src)?;
i18n_to_symbol.collect_i18n_usage(module_src, &module_ast)?;
symbol_to_route.collect_route_dependency(&module_ast, &symbol_dependency)?;

depend_on_graph.add_symbol_dependency(symbol_dependency)?;
scheduler.mark_candidate_as_parsed(c);
}
None => break,
}
}
Ok(UsedByGraph::from(&depend_on_graph))

let portable = Portable::new(
project_root.to_owned(),
i18n_to_symbol.table,
symbol_to_route.table,
UsedByGraph::from(&depend_on_graph),
);

let serialized = portable.export()?;
let mut file = File::create(&cli.output)?;
file.write_all(serialized.as_bytes())?;

Ok(())
}
145 changes: 31 additions & 114 deletions crates/dt_i18n/src/collect.rs
Original file line number Diff line number Diff line change
@@ -1,126 +1,43 @@
use super::core;
use anyhow::{bail, Context};
use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},
};
use swc_core::{
common::{sync::Lrc, Globals, Mark, SourceMap, GLOBALS},
ecma::{ast::*, transforms::base::resolver, visit::FoldWith},
};
use swc_ecma_parser::{parse_file_as_module, Syntax, TsSyntax};
use std::collections::{HashMap, HashSet};
use swc_core::ecma::ast::Module;

pub fn collect_all_translation_usage(
root: &str,
) -> anyhow::Result<HashMap<String, HashMap<String, HashSet<String>>>> {
// {
// "i18n.bird" => {
// "module path 1" => ["A", "B"],
// "module path 2" => ["Foo", "Bar"],
// },
// "i18n.cat" => {
// "module path 1" => ["A", "B", "C"],
// },
// }
let mut res: HashMap<String, HashMap<String, HashSet<String>>> = HashMap::new();
let all_paths = collect_all_paths(&PathBuf::from(root))?;
for path in all_paths.iter() {
let path_str = path.to_str().context("&PathBuf -> &str")?;
// - module path 1
// {
// "A" => ["i18n.bird", "i18n.cat"],
// "B" => ["i18n.bird", "i18n.cat"]
// "C" => ["i18n.cat"]
// }
// - module path 2
// {
// "Foo" => ["i18n.bird"]
// "Bar" => ["i18n.cat"]
// }
let translate_usage = get_translation_usage(path)?;
for (symbol, translation_keys) in translate_usage {
for translation_key in translation_keys.iter() {
if !res.contains_key(translation_key) {
res.insert(translation_key.to_owned(), HashMap::new());
pub struct I18nToSymbol {
pub table: HashMap<String, HashMap<String, HashSet<String>>>,
}

impl I18nToSymbol {
pub fn new() -> Self {
Self {
table: HashMap::new(),
}
}

pub fn collect_i18n_usage(
&mut self,
module_path: &str,
module_ast: &Module,
) -> anyhow::Result<()> {
let i18n_usage = core::collect_translation(module_ast)?;
for (symbol, i18n_keys) in i18n_usage {
for i18n_key in i18n_keys.iter() {
if !self.table.contains_key(i18n_key) {
self.table.insert(i18n_key.to_owned(), HashMap::new());
}
if !res.get(translation_key).unwrap().contains_key(path_str) {
res.get_mut(translation_key)
if !self.table.get(i18n_key).unwrap().contains_key(module_path) {
self.table
.get_mut(i18n_key)
.unwrap()
.insert(path_str.to_string(), HashSet::new());
.insert(module_path.to_string(), HashSet::new());
}
res.get_mut(translation_key)
self.table
.get_mut(i18n_key)
.unwrap()
.get_mut(path_str)
.get_mut(module_path)
.unwrap()
.insert(symbol.to_owned());
}
}
Ok(())
}

Ok(res)
}

fn collect_all_paths(root: &PathBuf) -> anyhow::Result<Vec<PathBuf>> {
let path = root.canonicalize()?;
let mut paths = vec![];

if path.is_dir() {
for entry in path.read_dir()? {
if let Ok(entry) = entry {
paths.append(&mut collect_all_paths(&entry.path())?);
}
}
return Ok(paths);
}

let path_str = path.to_str().context("path to str")?;
if path_str.ends_with(".js")
|| path_str.ends_with(".jsx")
|| path_str.ends_with(".ts")
|| path_str.ends_with(".tsx")
{
if !path_str.ends_with(".spec.js")
&& !path_str.ends_with(".spec.jsx")
&& !path_str.ends_with(".spec.ts")
&& !path_str.ends_with(".spec.tsx")
&& !path_str.ends_with(".test.js")
&& !path_str.ends_with(".test.jsx")
&& !path_str.ends_with(".test.ts")
&& !path_str.ends_with(".test.tsx")
{
paths.push(path.clone())
}
}
Ok(paths)
}

fn get_translation_usage(path: &PathBuf) -> anyhow::Result<HashMap<String, HashSet<String>>> {
let cm: Lrc<SourceMap> = Default::default();
let fm = cm
.load_file(Path::new(path))
.context(format!("failed to load {:?}", path))?;

let module = match parse_file_as_module(
&fm,
Syntax::Typescript(TsSyntax {
tsx: true,
decorators: true,
no_early_errors: true,
..Default::default()
}),
EsVersion::latest(),
None,
&mut Vec::new(),
) {
Ok(v) => v,
// We are not testing parser
Err(..) => bail!("failed to parse {:?}", path),
};

// This is how swc manages identifiers. ref: https://rustdoc.swc.rs/swc_ecma_transforms/fn.resolver.html
let module = GLOBALS.set(&Globals::new(), move || {
module.fold_with(&mut resolver(Mark::new(), Mark::new(), true))
});

Ok(core::collect_translation(&module)?)
}
2 changes: 1 addition & 1 deletion crates/dt_i18n/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ mod base_case_visitor;
mod collect;
mod core;

pub use collect::collect_all_translation_usage;
pub use collect::I18nToSymbol;
3 changes: 2 additions & 1 deletion crates/dt_portable/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ anyhow = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

dt_graph = { version = "0.1.0", path = "../dt_graph" }
dt_graph = { version = "0.1.0", path = "../dt_graph" }
dt_tracker = { version = "0.1.0", path = "../dt_tracker" }
16 changes: 13 additions & 3 deletions crates/dt_portable/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use dt_graph::used_by_graph::UsedByGraph;
use dt_tracker::ModuleSymbol;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};

Expand All @@ -15,7 +16,14 @@ pub struct Portable {
// "module path 1" => ["A", "B", "C"],
// },
// }
pub translation_usage: HashMap<String, HashMap<String, HashSet<String>>>,
pub i18n_to_symbol: HashMap<String, HashMap<String, HashSet<String>>>,

// {
// ("module path 1", LocalVar("A")) => ["/route/path/x", "/route/path/y"]
// ("module path 1", LocalVar("B")) => ["/route/path/x"]
// ("module path 2", LocalVar("A")) => ["/route/path/z"]
// }
pub symbol_to_route: HashMap<ModuleSymbol, Vec<String>>,

// {
// "module path 1" => {
Expand All @@ -30,12 +38,14 @@ pub struct Portable {
impl Portable {
pub fn new(
project_root: String,
translation_usage: HashMap<String, HashMap<String, HashSet<String>>>,
i18n_to_symbol: HashMap<String, HashMap<String, HashSet<String>>>,
symbol_to_route: HashMap<ModuleSymbol, Vec<String>>,
used_by_graph: UsedByGraph,
) -> Self {
Self {
project_root,
translation_usage,
i18n_to_symbol,
symbol_to_route,
used_by_graph,
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/dt_route/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use swc_core::ecma::{
#[derive(Debug)]
pub struct SymbolToRoutes {
// one symbol can be used in multiple routes
table: HashMap<ModuleSymbol, Vec<String>>,
pub table: HashMap<ModuleSymbol, Vec<String>>,
}

impl SymbolToRoutes {
Expand Down
4 changes: 2 additions & 2 deletions crates/dt_tracker/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use anyhow::{bail, Context};
use dt_graph::used_by_graph::{UsedBy, UsedByGraph, UsedByOther, UsedByType};
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Eq, PartialEq, Hash, Clone)]
#[derive(Debug, Eq, PartialEq, Hash, Clone, Deserialize)]
pub enum TraceTarget {
NamedExport(String),
DefaultExport,
Expand Down

0 comments on commit 4fc74dc

Please sign in to comment.