feat(minga): multi-lenguaje en parser — Python, TypeScript, JavaScript, Go
Minga deja de ser Rust-only. Cualquiera de los cinco dialectos
(Rust + 4 nuevos) se ingresa al CAS por su AST normalizado, hashea
estructuralmente, sincroniza por DHT como cualquier nodo. La
auto-deteccion por extension hace que minga ingest archivo.{py,ts,js,go}
"simplemente funcione".
API nueva en minga_core::parse:
- Funciones por dialecto: python, typescript, javascript, go (~6 LOC
c/u sobre el parse_with comun). Mas la rust existente.
- Enum Dialect con parse(source) y name() para logging.
- detect_by_extension(ext) -> Option<Dialect>: rs/py/pyi/ts/js/mjs/
cjs/go (case-insensitive). None para extensiones desconocidas.
Wire en minga-cli:
- cmd_ingest deja de hardcodear parse::rust — usa
detect_dialect(file)?.parse(...).
- initial_scan + cmd_watch cambian is_rs_file -> is_supported_source.
- CliError::UnsupportedLanguage { path, extension } nuevo, lista las
extensiones reconocidas en el mensaje.
Notas sobre hashing:
- Hashing estructural (cas::hash_node) funciona para todos. NO es
alpha-equivalente.
- Hashing alpha-equivalente (alpha::hash_node_alpha) sigue siendo
Rust-only — cada lenguaje tiene reglas distintas para binder vs
constructor; implementacion per-language queda como work futuro
(requiere conocimiento profundo de cada gramatica).
- Sanity test structural_hash_distinguishes_languages verifica que
"x = 1" parseado como Python != JS — las gramaticas no comparten
kinds, hashes salen distintos. Importante para evitar colisiones.
Deps nuevas (workspace + minga-core):
- tree-sitter-python 0.23, tree-sitter-typescript 0.23 (modo
LANGUAGE_TYPESCRIPT, no TSX), tree-sitter-javascript 0.23,
tree-sitter-go 0.23.
Tests: 9 nuevos en parse::tests (parse basico para 5 dialectos +
detect_by_extension canonical/case-insensitive + name() +
structural_hash_distinguishes_languages). 108 verdes en minga-core,
10 en minga-cli, sin regresion.
Pendientes: alpha-hashing per-language; alpha-Rust documentados en
alpha.rs (if let, while let, let-else, let-chains, or_pattern con
bindings).
This commit is contained in:
@@ -6,6 +6,76 @@ ratio/diff ver `git show <sha>`.
|
||||
|
||||
## 2026-05-09
|
||||
|
||||
### feat(minga): multi-lenguaje en parser — Python, TypeScript, JavaScript, Go
|
||||
Minga deja de ser Rust-only. Cualquiera de los cinco dialectos
|
||||
(Rust + 4 nuevos) se ingresa al CAS por su AST normalizado, hashea
|
||||
estructuralmente, sincroniza por DHT como cualquier nodo. La
|
||||
auto-detección por extensión hace que `minga ingest archivo.py` o
|
||||
`.ts` o `.go` "simplemente funcione".
|
||||
|
||||
API nueva en `minga_core::parse`:
|
||||
- Funciones por dialecto (~6 LOC c/u sobre el `parse_with` común):
|
||||
`python`, `typescript`, `javascript`, `go`. Más la `rust` existente.
|
||||
- Enum `Dialect` con `parse(source) -> Result<SemanticNode>` y
|
||||
`name() -> &'static str` para logging.
|
||||
- `detect_by_extension(ext) -> Option<Dialect>`: mapea `rs`/`py`/
|
||||
`pyi`/`ts`/`js`/`mjs`/`cjs`/`go` (case-insensitive). `None` para
|
||||
extensiones desconocidas — el caller decide si es error o se
|
||||
ignora silente.
|
||||
|
||||
Wire en `minga-cli`:
|
||||
- `cmd_ingest` deja de hardcodear `parse::rust` — usa
|
||||
`detect_dialect(file)?.parse(...)`. Acepta `.py`, `.ts`, `.js`,
|
||||
`.go` además de `.rs`.
|
||||
- `initial_scan` y `cmd_watch` cambian `is_rs_file` → `is_supported_source`
|
||||
para incluir todas las extensiones soportadas en el filtro.
|
||||
- `CliError::UnsupportedLanguage { path, extension }` nuevo, con
|
||||
mensaje que lista las extensiones reconocidas.
|
||||
|
||||
Notas sobre hashing:
|
||||
- El AST normalizado (`SemanticNode`) descarta whitespace y
|
||||
comentarios — propiedad universal de tree-sitter (extras). Misma
|
||||
lógica para los 5 dialectos.
|
||||
- Hashing **estructural** (`cas::hash_node`) funciona para todos:
|
||||
dos textos semánticamente equivalentes-por-estructura producen el
|
||||
mismo hash. NO α-equivalente (las variables ligadas distinguen).
|
||||
- Hashing **α-equivalente** (`alpha::hash_node_alpha`) sigue siendo
|
||||
Rust-only: cada lenguaje tiene reglas distintas para qué es
|
||||
binder vs. constructor (def/lambda en Python, arrow functions en
|
||||
TS/JS, func + closures en Go). Implementación per-language queda
|
||||
como work futuro — requiere conocimiento profundo de cada
|
||||
gramática y no se plantilla genéricamente.
|
||||
- Sanity test `structural_hash_distinguishes_languages` verifica
|
||||
que `x = 1` parseado como Python ≠ parseado como JavaScript: las
|
||||
gramáticas no comparten kinds y los hashes salen distintos.
|
||||
Importante para evitar colisiones cuando el mismo source se
|
||||
ingresa bajo dialectos distintos.
|
||||
|
||||
Deps nuevas (workspace + minga-core):
|
||||
- `tree-sitter-python = "0.23"`
|
||||
- `tree-sitter-typescript = "0.23"` (sólo el modo `LANGUAGE_TYPESCRIPT`,
|
||||
no TSX — bumpear a TSX es agregar otro dialecto cuando se necesite).
|
||||
- `tree-sitter-javascript = "0.23"`
|
||||
- `tree-sitter-go = "0.23"`
|
||||
|
||||
Tests:
|
||||
- 9 nuevos en `parse::tests`: parse básico para los 5 dialectos
|
||||
(Python con type hints, TS con tipos, JS sin tipos, Go con
|
||||
package declaration), `detect_by_extension` canonical +
|
||||
case-insensitive, `dialect_name`, `structural_hash_distinguishes_languages`.
|
||||
- 108 tests verdes en minga-core (39 → 48 unit + integration tests
|
||||
pre-existentes intactos).
|
||||
- 10 tests verdes en minga-cli (sin regresión en el path Rust;
|
||||
el refactor a `detect_dialect`/`is_supported_source` no rompe
|
||||
nada).
|
||||
|
||||
Pendientes futuros del changelog:
|
||||
- α-hashing per-language (Python: def/lambda/comprehensions;
|
||||
TS/JS: function/arrow/destructuring; Go: func/closure). Trabajo
|
||||
profundo, scope independiente.
|
||||
- α-Rust pendientes documentados en `alpha.rs`: `if let`,
|
||||
`while let`, `let-else`, let-chains, `or_pattern` con bindings.
|
||||
|
||||
### feat(brahman-handshake): multi-key identity — rotación de session sin perder peer_id lógico
|
||||
Cierra el último pendiente del plan de red P2P. Hasta ahora, rotar
|
||||
la keypair libp2p de un nodo cambiaba su `peer_id`, lo que
|
||||
|
||||
Generated
+44
@@ -5874,7 +5874,11 @@ dependencies = [
|
||||
"serde-big-array",
|
||||
"thiserror 2.0.18",
|
||||
"tree-sitter",
|
||||
"tree-sitter-go",
|
||||
"tree-sitter-javascript",
|
||||
"tree-sitter-python",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10335,12 +10339,42 @@ dependencies = [
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-go"
|
||||
version = "0.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b13d476345220dbe600147dd444165c5791bf85ef53e28acbedd46112ee18431"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-javascript"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf40bf599e0416c16c125c3cec10ee5ddc7d1bb8b0c60fa5c4de249ad34dc1b1"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-language"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782"
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-python"
|
||||
version = "0.23.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-rust"
|
||||
version = "0.23.3"
|
||||
@@ -10351,6 +10385,16 @@ dependencies = [
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-typescript"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trice"
|
||||
version = "0.4.0"
|
||||
|
||||
@@ -148,6 +148,10 @@ libp2p-allow-block-list = "0.6"
|
||||
# === Code parsing (minga) ===
|
||||
tree-sitter = "0.24"
|
||||
tree-sitter-rust = "0.23"
|
||||
tree-sitter-python = "0.23"
|
||||
tree-sitter-typescript = "0.23"
|
||||
tree-sitter-javascript = "0.23"
|
||||
tree-sitter-go = "0.23"
|
||||
|
||||
# === FS notify ===
|
||||
notify = "6.1"
|
||||
|
||||
@@ -83,7 +83,8 @@ pub fn cmd_ingest(
|
||||
let repo = PersistentRepo::open(repo_path.join(REPO_DIRNAME))?;
|
||||
|
||||
let source = fs::read_to_string(file)?;
|
||||
let node = parse::rust(&source)?;
|
||||
let dialect = detect_dialect(file)?;
|
||||
let node = dialect.parse(&source)?;
|
||||
let hash = repo.nodes.put(&node)?;
|
||||
repo.mst.insert(hash)?;
|
||||
repo.attestations
|
||||
@@ -96,6 +97,21 @@ pub fn cmd_ingest(
|
||||
})
|
||||
}
|
||||
|
||||
/// Detecta el dialecto desde la extensión del archivo. Error si la
|
||||
/// extensión no corresponde a un lenguaje soportado.
|
||||
fn detect_dialect(file: &Path) -> Result<parse::Dialect, CliError> {
|
||||
let ext = file
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("");
|
||||
parse::detect_by_extension(ext).ok_or_else(|| {
|
||||
CliError::UnsupportedLanguage {
|
||||
path: file.to_path_buf(),
|
||||
extension: ext.to_string(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// `minga listen <addr>`: arranca el peer, escucha en `addr`, y
|
||||
/// acepta sincronizaciones entrantes hasta que el proceso se cierre.
|
||||
pub async fn cmd_listen(
|
||||
@@ -191,7 +207,7 @@ pub async fn cmd_watch(
|
||||
continue;
|
||||
}
|
||||
for path in &event.paths {
|
||||
if is_rs_file(path) {
|
||||
if is_supported_source(path) {
|
||||
match ingest_into_repo(&repo, &keypair, path) {
|
||||
Ok(hash) => {
|
||||
eprintln!("ingerido: {} → {}", path.display(), hash);
|
||||
@@ -213,7 +229,7 @@ fn initial_scan(repo: &PersistentRepo, keypair: &Keypair, dir: &Path) {
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let p = entry.path();
|
||||
if is_rs_file(&p) {
|
||||
if is_supported_source(&p) {
|
||||
let _ = ingest_into_repo(repo, keypair, &p);
|
||||
}
|
||||
}
|
||||
@@ -225,7 +241,8 @@ fn ingest_into_repo(
|
||||
file: &Path,
|
||||
) -> Result<ContentHash, CliError> {
|
||||
let source = fs::read_to_string(file)?;
|
||||
let node = parse::rust(&source)?;
|
||||
let dialect = detect_dialect(file)?;
|
||||
let node = dialect.parse(&source)?;
|
||||
let hash = repo.nodes.put(&node)?;
|
||||
repo.mst.insert(hash)?;
|
||||
repo.attestations
|
||||
@@ -234,8 +251,14 @@ fn ingest_into_repo(
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
fn is_rs_file(path: &Path) -> bool {
|
||||
path.extension().and_then(|e| e.to_str()) == Some("rs") && path.is_file()
|
||||
/// Detecta si un archivo debe ingerirse: existe, es regular, y su
|
||||
/// extensión corresponde a un dialecto soportado.
|
||||
fn is_supported_source(path: &Path) -> bool {
|
||||
if !path.is_file() {
|
||||
return false;
|
||||
}
|
||||
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
|
||||
parse::detect_by_extension(ext).is_some()
|
||||
}
|
||||
|
||||
fn is_relevant_event(event: ¬ify::Event) -> bool {
|
||||
|
||||
@@ -40,4 +40,10 @@ pub enum CliError {
|
||||
|
||||
#[error("notify (file watcher): {0}")]
|
||||
Notify(#[from] notify::Error),
|
||||
|
||||
#[error(
|
||||
"lenguaje no soportado para {path}: extensión '{extension}' no mapea \
|
||||
a ningún dialecto conocido (rs, py, pyi, ts, js, mjs, cjs, go)"
|
||||
)]
|
||||
UnsupportedLanguage { path: PathBuf, extension: String },
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ description = "Minga core: semantic AST, content addressing, Merkle Search Tree.
|
||||
[dependencies]
|
||||
tree-sitter = { workspace = true }
|
||||
tree-sitter-rust = { workspace = true }
|
||||
tree-sitter-python = { workspace = true }
|
||||
tree-sitter-typescript = { workspace = true }
|
||||
tree-sitter-javascript = { workspace = true }
|
||||
tree-sitter-go = { workspace = true }
|
||||
blake3 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
ed25519-dalek = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,29 @@
|
||||
//! Adaptadores de parsing por dialecto. Hoy: Rust vía tree-sitter-rust.
|
||||
//! Adaptadores de parsing por dialecto.
|
||||
//!
|
||||
//! `parse::rust` produce un `SemanticNode` normalizado a partir de una
|
||||
//! cadena de código fuente. El error es opaco a propósito: el caller no
|
||||
//! necesita distinguir "gramática inválida" de "fallo del parser".
|
||||
//! Cada función devuelve un [`SemanticNode`] normalizado a partir del
|
||||
//! source code. La normalización vive en `ast::SemanticNode::from_tree_sitter`
|
||||
//! y es agnóstica al lenguaje — cualquier tree-sitter grammar produce
|
||||
//! el mismo shape de árbol semántico (sin whitespace, sin comentarios).
|
||||
//!
|
||||
//! Lenguajes soportados (cada uno son ~6 LOC + dep tree-sitter-X):
|
||||
//! - [`rust`] — Rust completo (con α-hashing en `alpha::hash_node_alpha`).
|
||||
//! - [`python`] — Python 3.x.
|
||||
//! - [`typescript`] — TypeScript (no TSX).
|
||||
//! - [`javascript`] — JavaScript / ECMAScript.
|
||||
//! - [`go`] — Go.
|
||||
//!
|
||||
//! Para hashing α-equivalente, sólo Rust tiene implementación dedicada
|
||||
//! hoy. Otros lenguajes caen al [`crate::cas::hash_node`] estructural,
|
||||
//! que es α-NO-equivalente: dos versiones del mismo término que
|
||||
//! difieren en nombres de variables ligadas tendrán hashes distintos.
|
||||
//! Suficiente para detección de cambios; no para detección de
|
||||
//! equivalencia semántica.
|
||||
//!
|
||||
//! ## Auto-detección por extensión
|
||||
//!
|
||||
//! [`detect_by_extension`] mapea `.rs` → Rust, `.py` → Python, etc.
|
||||
//! Útil para `minga ingest` cuando el caller no quiere especificar
|
||||
//! el dialecto a mano.
|
||||
|
||||
use crate::ast::SemanticNode;
|
||||
use thiserror::Error;
|
||||
@@ -16,10 +37,183 @@ pub enum ParseError {
|
||||
NoTree,
|
||||
}
|
||||
|
||||
pub fn rust(source: &str) -> Result<SemanticNode, ParseError> {
|
||||
let lang: Language = tree_sitter_rust::LANGUAGE.into();
|
||||
/// Identificadores estables de cada dialecto soportado.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Dialect {
|
||||
Rust,
|
||||
Python,
|
||||
TypeScript,
|
||||
JavaScript,
|
||||
Go,
|
||||
}
|
||||
|
||||
impl Dialect {
|
||||
/// Nombre canónico para logging / display.
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
Dialect::Rust => "rust",
|
||||
Dialect::Python => "python",
|
||||
Dialect::TypeScript => "typescript",
|
||||
Dialect::JavaScript => "javascript",
|
||||
Dialect::Go => "go",
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsea `source` con la gramática de este dialecto.
|
||||
pub fn parse(self, source: &str) -> Result<SemanticNode, ParseError> {
|
||||
match self {
|
||||
Dialect::Rust => rust(source),
|
||||
Dialect::Python => python(source),
|
||||
Dialect::TypeScript => typescript(source),
|
||||
Dialect::JavaScript => javascript(source),
|
||||
Dialect::Go => go(source),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mapea una extensión de archivo (sin el `.`) al dialecto correspondiente.
|
||||
/// `None` si la extensión no corresponde a un lenguaje soportado.
|
||||
///
|
||||
/// ```
|
||||
/// use minga_core::parse::{detect_by_extension, Dialect};
|
||||
/// assert_eq!(detect_by_extension("rs"), Some(Dialect::Rust));
|
||||
/// assert_eq!(detect_by_extension("py"), Some(Dialect::Python));
|
||||
/// assert_eq!(detect_by_extension("unknown"), None);
|
||||
/// ```
|
||||
pub fn detect_by_extension(ext: &str) -> Option<Dialect> {
|
||||
match ext.to_ascii_lowercase().as_str() {
|
||||
"rs" => Some(Dialect::Rust),
|
||||
"py" | "pyi" => Some(Dialect::Python),
|
||||
"ts" => Some(Dialect::TypeScript),
|
||||
"js" | "mjs" | "cjs" => Some(Dialect::JavaScript),
|
||||
"go" => Some(Dialect::Go),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_with(lang: Language, source: &str) -> Result<SemanticNode, ParseError> {
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(&lang).map_err(|_| ParseError::Language)?;
|
||||
let tree = parser.parse(source, None).ok_or(ParseError::NoTree)?;
|
||||
Ok(SemanticNode::from_tree_sitter(tree.root_node(), source.as_bytes()))
|
||||
}
|
||||
|
||||
pub fn rust(source: &str) -> Result<SemanticNode, ParseError> {
|
||||
parse_with(tree_sitter_rust::LANGUAGE.into(), source)
|
||||
}
|
||||
|
||||
pub fn python(source: &str) -> Result<SemanticNode, ParseError> {
|
||||
parse_with(tree_sitter_python::LANGUAGE.into(), source)
|
||||
}
|
||||
|
||||
pub fn typescript(source: &str) -> Result<SemanticNode, ParseError> {
|
||||
parse_with(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(), source)
|
||||
}
|
||||
|
||||
pub fn javascript(source: &str) -> Result<SemanticNode, ParseError> {
|
||||
parse_with(tree_sitter_javascript::LANGUAGE.into(), source)
|
||||
}
|
||||
|
||||
pub fn go(source: &str) -> Result<SemanticNode, ParseError> {
|
||||
parse_with(tree_sitter_go::LANGUAGE.into(), source)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_parses(d: Dialect, source: &str) -> SemanticNode {
|
||||
let node = d.parse(source).expect("parse should succeed");
|
||||
// Sanity: el root siempre tiene al menos un child para code real.
|
||||
assert!(
|
||||
!node.children.is_empty(),
|
||||
"{}: root node sin children — parse posiblemente vacío",
|
||||
d.name()
|
||||
);
|
||||
node
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_parses_basic() {
|
||||
assert_parses(Dialect::Rust, "fn add(a: i32, b: i32) -> i32 { a + b }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_parses_basic() {
|
||||
assert_parses(
|
||||
Dialect::Python,
|
||||
"def add(a: int, b: int) -> int:\n return a + b\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typescript_parses_basic() {
|
||||
assert_parses(
|
||||
Dialect::TypeScript,
|
||||
"function add(a: number, b: number): number { return a + b; }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn javascript_parses_basic() {
|
||||
assert_parses(
|
||||
Dialect::JavaScript,
|
||||
"function add(a, b) { return a + b; }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn go_parses_basic() {
|
||||
assert_parses(
|
||||
Dialect::Go,
|
||||
"package main\n\nfunc add(a, b int) int {\n return a + b\n}\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_extension_canonical() {
|
||||
assert_eq!(detect_by_extension("rs"), Some(Dialect::Rust));
|
||||
assert_eq!(detect_by_extension("py"), Some(Dialect::Python));
|
||||
assert_eq!(detect_by_extension("pyi"), Some(Dialect::Python));
|
||||
assert_eq!(detect_by_extension("ts"), Some(Dialect::TypeScript));
|
||||
assert_eq!(detect_by_extension("js"), Some(Dialect::JavaScript));
|
||||
assert_eq!(detect_by_extension("mjs"), Some(Dialect::JavaScript));
|
||||
assert_eq!(detect_by_extension("cjs"), Some(Dialect::JavaScript));
|
||||
assert_eq!(detect_by_extension("go"), Some(Dialect::Go));
|
||||
assert_eq!(detect_by_extension("unknown"), None);
|
||||
assert_eq!(detect_by_extension(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_extension_case_insensitive() {
|
||||
assert_eq!(detect_by_extension("RS"), Some(Dialect::Rust));
|
||||
assert_eq!(detect_by_extension("Py"), Some(Dialect::Python));
|
||||
assert_eq!(detect_by_extension("TS"), Some(Dialect::TypeScript));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dialect_name_canonical() {
|
||||
assert_eq!(Dialect::Rust.name(), "rust");
|
||||
assert_eq!(Dialect::Python.name(), "python");
|
||||
assert_eq!(Dialect::TypeScript.name(), "typescript");
|
||||
assert_eq!(Dialect::JavaScript.name(), "javascript");
|
||||
assert_eq!(Dialect::Go.name(), "go");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structural_hash_distinguishes_languages() {
|
||||
// Mismo "shape" textual pero distintos lenguajes producen
|
||||
// árboles distintos (las gramáticas no coinciden) y por tanto
|
||||
// hashes estructurales distintos. Importante para evitar
|
||||
// colisiones en el CAS cuando el mismo source se ingiere
|
||||
// bajo dialectos distintos.
|
||||
use crate::cas::hash_node;
|
||||
let py = Dialect::Python.parse("x = 1").unwrap();
|
||||
let js = Dialect::JavaScript.parse("x = 1").unwrap();
|
||||
assert_ne!(
|
||||
hash_node(&py),
|
||||
hash_node(&js),
|
||||
"py y js deberían tener hashes distintos para el mismo source"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user