refactor: tombi-wasm.

This commit is contained in:
ya7010 2025-07-04 00:11:56 +09:00
parent 6482be30f4
commit ae681536a9
15 changed files with 153 additions and 163 deletions

15
Cargo.lock generated
View file

@ -2893,6 +2893,17 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
@ -3595,6 +3606,7 @@ dependencies = [
"tower-lsp",
"tracing",
"tracing-subscriber",
"wasm-bindgen",
]
[[package]]
@ -4002,6 +4014,7 @@ dependencies = [
"serde",
"tower-lsp",
"tracing",
"wasm-bindgen",
]
[[package]]
@ -4062,6 +4075,7 @@ dependencies = [
"log",
"nu-ansi-term 0.50.1",
"serde",
"serde-wasm-bindgen",
"serde_json",
"serde_tombi",
"tokio",
@ -4071,6 +4085,7 @@ dependencies = [
"tombi-future",
"tombi-lexer",
"tombi-schema-store",
"tombi-text",
"tombi-url",
"tracing",
"tracing-subscriber",

View file

@ -12,6 +12,7 @@ thiserror.workspace = true
tombi-text.workspace = true
tower-lsp = { workspace = true, optional = true }
tracing.workspace = true
wasm-bindgen = { workspace = true, optional = true }
[dev-dependencies]
clap.workspace = true
@ -21,3 +22,4 @@ tracing-subscriber.workspace = true
[features]
default = ["lsp"]
lsp = ["dep:tower-lsp"]
wasm = ["dep:wasm-bindgen"]

View file

@ -6,6 +6,8 @@ pub use printer::Print;
use tower_lsp::lsp_types::NumberOrString;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
#[cfg_attr(feature = "wasm", derive(serde::Serialize))]
pub struct Diagnostic {
level: level::Level,
code: String,

View file

@ -11,7 +11,6 @@ use countme::Count;
use crate::{
arc::{Arc, HeaderSlice, ThinArc},
green::{GreenElement, GreenElementRef, SyntaxKind},
utility_types::static_assert,
GreenToken, NodeOrToken,
};
@ -38,7 +37,7 @@ pub(crate) enum GreenChild {
}
#[cfg(target_pointer_width = "64")]
static_assert!(mem::size_of::<GreenChild>() == mem::size_of::<usize>() * 3);
crate::utility_types::static_assert!(mem::size_of::<GreenChild>() == mem::size_of::<usize>() * 3);
type Repr = HeaderSlice<GreenNodeHead, [GreenChild]>;
type ReprThin = HeaderSlice<GreenNodeHead, [GreenChild; 0]>;

View file

@ -156,6 +156,7 @@ macro_rules! _static_assert {
};
}
#[allow(unused_imports)]
pub(crate) use _static_assert as static_assert;
#[derive(Copy, Clone, Debug)]

View file

@ -9,6 +9,7 @@ license.workspace = true
serde = { workspace = true, optional = true }
tower-lsp = { workspace = true, optional = true }
tracing.workspace = true
wasm-bindgen = { workspace = true, optional = true }
[dev-dependencies]
pretty_assertions.workspace = true
@ -18,3 +19,4 @@ rstest.workspace = true
default = ["lsp", "serde"]
lsp = ["dep:tower-lsp"]
serde = ["dep:serde"]
wasm = ["dep:wasm-bindgen", "serde"]

View file

@ -6,6 +6,8 @@ use std::{
use crate::{Column, Line, RelativePosition};
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
#[cfg_attr(feature = "wasm", derive(serde::Serialize))]
pub struct Position {
pub line: Line,
pub column: Column,

View file

@ -3,6 +3,8 @@ use std::ops::{Add, AddAssign};
use crate::{Column, Line, Position, RelativePosition};
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
#[cfg_attr(feature = "wasm", derive(serde::Serialize))]
pub struct Range {
// Invariant: start <= end
pub start: Position,

View file

@ -41,6 +41,17 @@ macro_rules! define_toml_version {
}
}
}
impl TomlVersion {
pub fn from_str(value: &str) -> Option<Self> {
match value {
$(
$version => Some(Self::$variant),
)*
_ => None,
}
}
}
};
}

View file

@ -129,37 +129,38 @@ pub fn try_from_url(config_url: &url::Url) -> Result<Option<Config>, tombi_confi
/// Load the config from the current directory.
pub fn load_with_path() -> Result<(Config, Option<std::path::PathBuf>), tombi_config::Error> {
let mut current_dir = std::env::current_dir().unwrap();
loop {
let config_path = current_dir.join(TOMBI_TOML_FILENAME);
if config_path.is_file() {
tracing::debug!("\"{}\" found at {:?}", TOMBI_TOML_FILENAME, &config_path);
if let Ok(mut current_dir) = std::env::current_dir() {
loop {
let config_path = current_dir.join(TOMBI_TOML_FILENAME);
if config_path.is_file() {
tracing::debug!("\"{}\" found at {:?}", TOMBI_TOML_FILENAME, &config_path);
let Some(config) = try_from_path(&config_path)? else {
unreachable!("tombi.toml should always be parsed successfully.");
};
let Some(config) = try_from_path(&config_path)? else {
unreachable!("tombi.toml should always be parsed successfully.");
};
return Ok((config, Some(config_path)));
}
return Ok((config, Some(config_path)));
}
let pyproject_toml_path = current_dir.join(PYPROJECT_TOML_FILENAME);
if pyproject_toml_path.exists() {
tracing::debug!(
"\"{}\" found at {:?}",
PYPROJECT_TOML_FILENAME,
pyproject_toml_path
);
let pyproject_toml_path = current_dir.join(PYPROJECT_TOML_FILENAME);
if pyproject_toml_path.exists() {
tracing::debug!(
"\"{}\" found at {:?}",
PYPROJECT_TOML_FILENAME,
pyproject_toml_path
);
match try_from_path(&pyproject_toml_path)? {
Some(config) => return Ok((config, Some(pyproject_toml_path))),
None => {
tracing::debug!("No [tool.tombi] found in {:?}", &pyproject_toml_path);
}
};
}
match try_from_path(&pyproject_toml_path)? {
Some(config) => return Ok((config, Some(pyproject_toml_path))),
None => {
tracing::debug!("No [tool.tombi] found in {:?}", &pyproject_toml_path);
}
};
}
if !current_dir.pop() {
break;
if !current_dir.pop() {
break;
}
}
}

View file

@ -19,14 +19,15 @@ nu-ansi-term.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_tombi = { workspace = true, default-features = false, features = ["wasm"] }
# serde_tombi = { workspace = true, default-features = false }
serde-wasm-bindgen = "0.6.5"
tokio = { workspace = true, features = ["macros"] }
tombi-config.workspace = true
tombi-diagnostic.workspace = true
tombi-diagnostic = { workspace = true, features = ["wasm"] }
tombi-formatter.workspace = true
tombi-future = { workspace = true, features = ["wasm"] }
tombi-lexer.workspace = true
tombi-schema-store = { workspace = true, features = ["wasm"] }
tombi-text = { workspace = true, features = ["wasm"] }
tombi-url = { workspace = true, features = ["wasm"] }
tracing.workspace = true
tracing-subscriber.workspace = true

View file

@ -1,46 +0,0 @@
use crate::pretty_buf::PrettyBuf;
use itertools::Either::Right;
use std::path::PathBuf;
use tombi_config::{Config, TomlVersion};
use tombi_diagnostic::Print;
use tombi_formatter::formatter::definitions::FormatDefinitions;
use tombi_formatter::Formatter;
pub async fn format(target_path: PathBuf, target_content: String) -> Result<String, String> {
let toml_version = TomlVersion::V1_0_0;
let schema_store =
tombi_schema_store::SchemaStore::new_with_options(tombi_schema_store::Options {
offline: Some(false),
strict: None,
});
schema_store
.load_config(&Config::default(), None)
.await
.map_err(|e| e.to_string())?;
let options = Default::default();
let definitions = FormatDefinitions::default();
let formatter = Formatter::new(
toml_version,
&definitions,
&options,
Some(Right(&target_path)),
&schema_store,
);
let mut printer = PrettyBuf::new();
match formatter.format(&target_content).await {
Ok(formatted) => Ok(formatted),
Err(diagnostics) => {
diagnostics
.into_iter()
.map(|diagnostic| diagnostic.with_source_file(target_path.clone()))
.collect::<Vec<_>>()
.print(&mut printer);
Err(printer.get())
}
}
}

View file

@ -1,8 +1,9 @@
mod format;
mod pretty_buf;
use js_sys::Promise;
use wasm_bindgen::prelude::*;
use serde_wasm_bindgen;
use tombi_config::TomlVersion;
use tombi_diagnostic::Diagnostic;
use tombi_formatter::FormatDefinitions;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
use wasm_bindgen_futures::future_to_promise;
#[wasm_bindgen(start)]
@ -11,13 +12,83 @@ pub fn start() {
}
#[wasm_bindgen]
pub fn format(file_name: String, content: String) -> Promise {
let fut = format::format(std::path::PathBuf::from(file_name), content);
pub fn format(source: String, file_path: Option<String>, toml_version: Option<String>) -> Promise {
#[derive(serde::Serialize, Debug)]
#[serde(untagged)]
#[serde(rename_all = "camelCase")]
enum FormatError {
Error { error: String },
Diagnostics { diagnostics: Vec<Diagnostic> },
}
async fn inner_format(
source: String,
file_path: Option<String>,
toml_version: Option<String>,
) -> Result<String, FormatError> {
let toml_version = match toml_version {
Some(v) => match TomlVersion::from_str(&v) {
Some(v) => v,
None => {
return Err(FormatError::Error {
error: "Invalid TOML version".to_string(),
});
}
},
None => TomlVersion::default(),
};
let (config, config_path) = match serde_tombi::config::load_with_path() {
Ok((config, config_path)) => (config, config_path),
Err(err) => {
return Err(FormatError::Error {
error: err.to_string(),
});
}
};
let schema_options = config.schema.as_ref();
let schema_store =
tombi_schema_store::SchemaStore::new_with_options(tombi_schema_store::Options {
offline: None,
strict: schema_options.and_then(|schema_options| schema_options.strict()),
});
tracing::info!("config_path: {:?}", config_path);
tracing::info!("config: {:?}", config);
if let Err(error) = schema_store
.load_config(&config, config_path.as_deref())
.await
.map_err(|e| e.to_string())
{
return Err(FormatError::Error { error });
}
let format_options = config.format.clone().unwrap_or_default();
let format_definitions = FormatDefinitions::default();
let file_path_buf = file_path.map(|path| std::path::PathBuf::from(path));
match tombi_formatter::Formatter::new(
toml_version,
&format_definitions,
&format_options,
file_path_buf
.as_deref()
.map(|path| itertools::Either::Right(path)),
&schema_store,
)
.format(&source)
.await
{
Ok(t) => Ok(t),
Err(diagnostics) => Err(FormatError::Diagnostics { diagnostics }),
}
}
future_to_promise(async move {
match fut.await {
match inner_format(source, file_path, toml_version).await {
Ok(t) => Ok(JsValue::from_str(&t)),
Err(e) => Err(JsValue::from_str(&e)),
Err(e) => Err(serde_wasm_bindgen::to_value(&e).unwrap()),
}
})
}

View file

@ -1,74 +0,0 @@
use nu_ansi_term::{Color, Style};
use std::fmt;
use std::fmt::Write;
use std::sync::{Arc, Mutex};
use tombi_diagnostic::printer::Simple;
use tombi_diagnostic::{Diagnostic, Level, Print};
#[derive(Debug, Clone)]
pub struct PrettyBuf(Arc<Mutex<String>>);
impl Default for PrettyBuf {
fn default() -> Self {
Self::new()
}
}
impl PrettyBuf {
pub fn new() -> Self {
Self(Arc::new(Mutex::new(String::new())))
}
pub fn get(&self) -> String {
self.0.lock().unwrap().clone()
}
}
impl Write for PrettyBuf {
fn write_str(&mut self, s: &str) -> fmt::Result {
let mut guard = self.0.lock().unwrap();
guard.push_str(s);
Ok(())
}
}
impl Print<PrettyBuf> for Level {
fn print(&self, _printer: &mut PrettyBuf) {
self.print(&mut Simple);
}
}
impl Print<PrettyBuf> for Diagnostic {
fn print(&self, printer: &mut PrettyBuf) {
self.level().print(printer);
writeln!(printer, ": {}", Style::new().bold().paint(self.message())).unwrap();
let at_style: Style = Style::new().fg(Color::DarkGray);
let link_style: Style = Style::new().fg(Color::Cyan);
if let Some(source_file) = self.source_file() {
writeln!(
printer,
" {} {}",
at_style.paint("at"),
link_style.paint(format!(
"{}:{}:{}",
source_file.display(),
self.position().line + 1,
self.position().column + 1
)),
)
.unwrap();
} else {
writeln!(
printer,
" {}",
at_style.paint(format!(
"at line {} column {}",
self.position().line + 1,
self.position().column + 1
)),
)
.unwrap();
}
}
}

View file

@ -35,15 +35,16 @@
init().then(() => {
document.getElementById("run").onclick = async () => {
const file = document.getElementById("file-name").value;
const filename = document.getElementById("file-name").value;
const text = document.getElementById("content").value;
const output = document.getElementById("output");
try {
const result = await format(file, text);
output.textContent = result;
} catch (e) {
output.textContent = "Error: " + e;
const result = await format(text, filename);
output.textContent = result.toString();
} catch (error) {
output.textContent = JSON.stringify(error, null, 2);
}
};
});