├── .gitignore ├── src ├── fixtures │ ├── c.tsx │ ├── index.js │ ├── main.ts │ ├── a.ts │ └── b.ts ├── lib.rs ├── context.rs ├── stats │ ├── file_length.rs │ ├── missing_switch_default.rs │ ├── class_data_abstraction_coupling.rs │ ├── binary_expression_complexity.rs │ ├── parameter_number.rs │ ├── anon_inner_length.rs │ ├── function_length.rs │ └── cyclomatic_complexity.rs ├── analysis.rs ├── stats.rs ├── utils │ └── log.ts ├── commands │ └── stat.ts ├── resolve.rs ├── project.rs └── walker.rs ├── lib ├── nocuous_bg.wasm ├── nocuous.generated.d.ts └── nocuous.generated.js ├── rust-toolchain.toml ├── .vscode └── settings.json ├── .rustfmt.toml ├── .github └── workflows │ ├── publish.yml │ └── ci.yml ├── cli.ts ├── Cargo.toml ├── LICENSE ├── deno.json ├── test.ts ├── README.md ├── mod.ts └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /src/fixtures/c.tsx: -------------------------------------------------------------------------------- 1 | export function C() { 2 | return
; 3 | } 4 | -------------------------------------------------------------------------------- /lib/nocuous_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h-o-t/nocuous/HEAD/lib/nocuous_bg.wasm -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.80.1" 3 | components = ["rustfmt", "clippy"] 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "cSpell.words": [ 5 | "indexmap" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2020-2021 the Kitson P. Kelly. All rights reserved. MIT license. 2 | max_width = 80 3 | tab_spaces = 2 4 | edition = "2018" 5 | -------------------------------------------------------------------------------- /src/fixtures/index.js: -------------------------------------------------------------------------------- 1 | import { A } from "./a.ts"; 2 | import { B } from "./b.ts"; 3 | import { C } from "./c.tsx"; 4 | 5 | new A(); 6 | new B(); 7 | C(); 8 | -------------------------------------------------------------------------------- /src/fixtures/main.ts: -------------------------------------------------------------------------------- 1 | import { A } from "./a.ts"; 2 | import { B } from "./b.ts"; 3 | import { C } from "./c.tsx"; 4 | 5 | new A(); 6 | new B(); 7 | C(); 8 | -------------------------------------------------------------------------------- /src/fixtures/a.ts: -------------------------------------------------------------------------------- 1 | export class A { 2 | constructor() { 3 | const f = new F(); 4 | f.u; 5 | } 6 | } 7 | 8 | class F { 9 | #u = new Uint8Array(); 10 | 11 | get u() { 12 | return this.#u; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/fixtures/b.ts: -------------------------------------------------------------------------------- 1 | export class B { 2 | #a() { 3 | return undefined; 4 | } 5 | 6 | b() { 7 | return this.#a(); 8 | } 9 | 10 | c(a: string, b: string, c: string, d: string, e: string) { 11 | return `${a}${b}${c}${d}${e}`; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | 11 | permissions: 12 | contents: read 13 | id-token: write 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Publish package 19 | run: npx jsr publish 20 | -------------------------------------------------------------------------------- /cli.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module 3 | */ 4 | 5 | import { Command } from "@cliffy/command"; 6 | import stat from "./src/commands/stat.ts"; 7 | 8 | await new Command() 9 | .name("nocuous") 10 | .version("1.0.1") 11 | .action(function () { 12 | this.showHelp(); 13 | }) 14 | .description("Static code analysis for JavaScript and TypeScript.") 15 | .command("stat", stat) 16 | .parse(Deno.args); 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nocuous" 3 | version = "1.1.0" 4 | edition = "2021" 5 | description = "Wasm bindings to peform static analysis of JavaScript and TypeScript code" 6 | authors = ["Kitson P. Kelly"] 7 | license = "MIT" 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | name = "nocuous" 12 | 13 | [dependencies] 14 | anyhow = "1.0.75" 15 | deno_ast = { version = "0.42.1", features = ["cjs", "dep_analysis", "view", "visit"] } 16 | futures = "0.3.28" 17 | import_map = "0.20.1" 18 | indexmap = { version = "2.0.0", features = ["serde"] } 19 | js-sys = "0.3.64" 20 | lazy_static = "1.4.0" 21 | regex = "1.9.4" 22 | serde = { version = "1.0", features = ["derive"] } 23 | serde-wasm-bindgen = "0.6.5" 24 | url = "2.4.1" 25 | wasm-bindgen = { version = "=0.2.92", features = ["serde-serialize"] } 26 | wasm-bindgen-futures = "=0.4.42" 27 | 28 | [profile.release] 29 | codegen-units = 1 30 | incremental = true 31 | lto = true 32 | opt-level = "s" 33 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod analysis; 2 | mod context; 3 | mod project; 4 | mod resolve; 5 | mod stats; 6 | mod walker; 7 | 8 | use wasm_bindgen::prelude::*; 9 | 10 | use crate::resolve::resolve_url_or_path; 11 | 12 | #[wasm_bindgen] 13 | extern "C" { 14 | #[wasm_bindgen(js_namespace = console)] 15 | fn log(s: &str); 16 | } 17 | 18 | fn to_js_error(error: anyhow::Error) -> JsError { 19 | JsError::new(&error.to_string()) 20 | } 21 | 22 | #[wasm_bindgen] 23 | pub async fn stats( 24 | roots: JsValue, 25 | load: js_sys::Function, 26 | maybe_resolve: Option, 27 | ) -> Result { 28 | let roots: Vec = serde_wasm_bindgen::from_value(roots)?; 29 | let roots = roots 30 | .iter() 31 | .filter_map(|root| resolve_url_or_path(root).ok()) 32 | .collect(); 33 | let mut project = project::Project::new(roots, load, maybe_resolve); 34 | let stats = project.stat().await.map_err(to_js_error)?; 35 | serde_wasm_bindgen::to_value(&stats).map_err(|err| err.into()) 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | nocuous: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | version: [1.x, canary] 11 | steps: 12 | - name: clone repository 13 | uses: actions/checkout@v4 14 | 15 | - name: install Rust 16 | uses: dtolnay/rust-toolchain@stable 17 | 18 | - name: cache 19 | uses: Swatinem/rust-cache@v1 20 | 21 | - name: install Deno 22 | uses: denoland/setup-deno@v1 23 | with: 24 | deno-version: ${{ matrix.version }} 25 | 26 | - name: format 27 | run: | 28 | cargo fmt -- --check 29 | deno fmt --check 30 | 31 | - name: lint 32 | run: | 33 | cargo clippy --locked --release --all-features --all-targets 34 | deno lint 35 | 36 | - name: build 37 | run: cargo build 38 | 39 | - name: test 40 | run: deno task test 41 | 42 | - name: check wasm build 43 | run: deno task build --check 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 - 2024 Kitson P. Kelly 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@higher-order-testing/nocuous", 3 | "version": "1.1.0", 4 | "exports": { 5 | ".": "./mod.ts", 6 | "./cli": "./cli.ts" 7 | }, 8 | "publish": { 9 | "exclude": [ 10 | ".github", 11 | ".rustfmt.toml", 12 | ".vscode", 13 | "Cargo.lock", 14 | "Cargo.toml", 15 | "rust-toolchain.toml", 16 | "test.ts", 17 | "src/fixtures" 18 | ] 19 | }, 20 | "imports": { 21 | "@cliffy/ansi": "jsr:@cliffy/ansi@1.0.0-rc.7", 22 | "@cliffy/command": "jsr:@cliffy/command@1.0.0-rc.7", 23 | "@cliffy/table": "jsr:@cliffy/table@1.0.0-rc.7", 24 | "@denosaurs/wait": "jsr:@denosaurs/wait@^0.2.2", 25 | "@std/assert": "jsr:@std/assert@^1", 26 | "@std/path": "jsr:@std/path@^1" 27 | }, 28 | "fmt": { 29 | "exclude": ["target", "lib"] 30 | }, 31 | "lint": { 32 | "exclude": ["target", "lib"] 33 | }, 34 | "lock": false, 35 | "tasks": { 36 | "build": "deno run -A jsr:@deno/wasmbuild@0.17.2", 37 | "cli": "deno run --allow-read --allow-net cli.ts", 38 | "test": "deno task test:rust && deno task test:deno", 39 | "test:deno": "deno test --allow-read=..", 40 | "test:rust": "cargo test" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::stats::StatRecord; 2 | 3 | use deno_ast::view; 4 | use deno_ast::ParsedSource; 5 | 6 | #[derive(Debug)] 7 | pub struct TraversalController(bool); 8 | 9 | impl TraversalController { 10 | fn new() -> Self { 11 | Self(false) 12 | } 13 | 14 | pub fn reset(&mut self) { 15 | self.0 = false; 16 | } 17 | 18 | pub fn should_skip(&mut self) -> bool { 19 | let skip = self.0; 20 | self.reset(); 21 | skip 22 | } 23 | 24 | pub fn skip(&mut self) { 25 | self.0 = true; 26 | } 27 | } 28 | 29 | pub struct Context<'view> { 30 | pub parsed_source: ParsedSource, 31 | pub program: view::Program<'view>, 32 | stats: Vec, 33 | pub traversal: TraversalController, 34 | } 35 | 36 | impl<'view> Context<'view> { 37 | pub fn new( 38 | parsed_source: ParsedSource, 39 | program: view::Program<'view>, 40 | ) -> Self { 41 | Self { 42 | parsed_source, 43 | program, 44 | stats: Vec::new(), 45 | traversal: TraversalController::new(), 46 | } 47 | } 48 | 49 | pub fn add_stat(&mut self, stat: StatRecord) { 50 | self.stats.push(stat); 51 | } 52 | 53 | pub fn take(self) -> Vec { 54 | self.stats 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/stats/file_length.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use super::Stat; 4 | use super::StatLevel; 5 | use super::StatRecord; 6 | use crate::analysis::lines_of_code; 7 | use crate::context::Context; 8 | 9 | const CODE: &str = "file-length"; 10 | const SHORT_CODE: &str = "L"; 11 | 12 | #[derive(Debug)] 13 | pub struct FileLength; 14 | 15 | impl Stat for FileLength { 16 | fn new() -> Arc { 17 | Arc::new(Self) 18 | } 19 | 20 | fn code(&self) -> &'static str { 21 | CODE 22 | } 23 | 24 | fn short_code(&self) -> &'static str { 25 | SHORT_CODE 26 | } 27 | 28 | fn stat<'view>( 29 | &self, 30 | context: &mut Context<'view>, 31 | maybe_threshold: Option, 32 | ) { 33 | let threshold = maybe_threshold.unwrap_or(500); 34 | let code = context.parsed_source.text_info_lazy().text(); 35 | let line_count = lines_of_code(code.as_ref()); 36 | let score = if line_count >= threshold { 37 | line_count as f64 / threshold as f64 38 | } else { 39 | 0.0 40 | }; 41 | context.add_stat(StatRecord { 42 | metric: CODE.to_string(), 43 | metric_short: SHORT_CODE.to_string(), 44 | level: StatLevel::Module, 45 | threshold, 46 | count: 1, 47 | score, 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "@std/assert/equals"; 2 | import { join } from "@std/path"; 3 | import { asURL, instantiate, stats } from "./mod.ts"; 4 | 5 | Deno.test({ 6 | name: "generate stats - typescript base", 7 | async fn() { 8 | await instantiate(); 9 | const actual = await stats( 10 | new URL("./src/fixtures/main.ts", import.meta.url), 11 | ); 12 | assertEquals(actual.size, 4); 13 | }, 14 | }); 15 | 16 | Deno.test({ 17 | name: "generate stats - javascript base", 18 | async fn() { 19 | await instantiate(); 20 | const actual = await stats( 21 | new URL("./src/fixtures/index.js", import.meta.url), 22 | ); 23 | assertEquals(actual.size, 4); 24 | }, 25 | }); 26 | 27 | Deno.test({ 28 | name: "asURL - relative", 29 | fn() { 30 | const expected = new URL("mod.ts", import.meta.url); 31 | const actual = asURL("./mod.ts"); 32 | assertEquals(actual.toString(), expected.toString()); 33 | }, 34 | }); 35 | 36 | Deno.test({ 37 | name: "asURL - relative base supplied", 38 | fn() { 39 | const expected = new URL("test/mod.ts", import.meta.url); 40 | const actual = asURL("./mod.ts", join(Deno.cwd(), "test")); 41 | assertEquals(actual.toString(), expected.toString()); 42 | }, 43 | }); 44 | 45 | Deno.test({ 46 | name: "asURL - absolute", 47 | fn() { 48 | const expected = new URL("mod.ts", import.meta.url); 49 | const actual = asURL(join(Deno.cwd(), "./mod.ts")); 50 | assertEquals(actual.toString(), expected.toString()); 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /lib/nocuous.generated.d.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file 2 | // deno-fmt-ignore-file 3 | 4 | export interface InstantiateResult { 5 | instance: WebAssembly.Instance; 6 | exports: { 7 | stats: typeof stats 8 | }; 9 | } 10 | 11 | /** Gets if the Wasm module has been instantiated. */ 12 | export function isInstantiated(): boolean; 13 | 14 | /** Options for instantiating a Wasm instance. */ 15 | export interface InstantiateOptions { 16 | /** Optional url to the Wasm file to instantiate. */ 17 | url?: URL; 18 | /** Callback to decompress the raw Wasm file bytes before instantiating. */ 19 | decompress?: (bytes: Uint8Array) => Uint8Array; 20 | } 21 | 22 | /** Instantiates an instance of the Wasm module returning its functions. 23 | * @remarks It is safe to call this multiple times and once successfully 24 | * loaded it will always return a reference to the same object. */ 25 | export function instantiate(opts?: InstantiateOptions): Promise; 26 | 27 | /** Instantiates an instance of the Wasm module along with its exports. 28 | * @remarks It is safe to call this multiple times and once successfully 29 | * loaded it will always return a reference to the same object. */ 30 | export function instantiateWithInstance(opts?: InstantiateOptions): Promise; 31 | 32 | /** 33 | * @param {any} roots 34 | * @param {Function} load 35 | * @param {Function | undefined} [maybe_resolve] 36 | * @returns {Promise} 37 | */ 38 | export function stats(roots: any, load: Function, maybe_resolve?: Function): Promise; 39 | -------------------------------------------------------------------------------- /src/analysis.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use regex::Regex; 3 | 4 | lazy_static! { 5 | static ref RE_BLOCK_END: Regex = Regex::new(r"\*/").unwrap(); 6 | static ref RE_BLOCK_START: Regex = Regex::new(r"^\s*/\*").unwrap(); 7 | static ref RE_BLOCK_SINGLE: Regex = Regex::new(r"^\s*/\*.*\*/").unwrap(); 8 | static ref RE_NON_WHITESPACE: Regex = Regex::new(r"\S").unwrap(); 9 | static ref RE_TWOSLASH: Regex = Regex::new(r"^\s*/{2}").unwrap(); 10 | } 11 | 12 | /// Given a string, return the count of lines that are considered code in 13 | /// JavaScript or TypeScript, skipping any empty lines or lines that only 14 | /// contain comments. 15 | pub fn lines_of_code(code: &str) -> u32 { 16 | let mut count = 0_u32; 17 | let mut in_comment = false; 18 | for line in code.lines() { 19 | if in_comment { 20 | if RE_BLOCK_END.is_match(line) { 21 | in_comment = false; 22 | } 23 | continue; 24 | } 25 | if RE_TWOSLASH.is_match(line) || RE_BLOCK_SINGLE.is_match(line) { 26 | continue; 27 | } 28 | if RE_BLOCK_START.is_match(line) { 29 | in_comment = true; 30 | continue; 31 | } 32 | if RE_NON_WHITESPACE.is_match(line) { 33 | count += 1; 34 | } 35 | } 36 | count 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | 43 | #[test] 44 | fn correct_counts() { 45 | let actual = lines_of_code( 46 | r#"/** 47 | * Some sort of block comment. 48 | */ 49 | function a() { 50 | 51 | // a two line comment 52 | const b = "a"; 53 | 54 | return b; 55 | } 56 | 57 | /** a single line one. */ 58 | const c = 12345; 59 | 60 | // more twoslash 61 | 62 | "#, 63 | ); 64 | assert_eq!(actual, 5); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/stats.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::sync::Arc; 3 | 4 | use crate::context::Context; 5 | 6 | mod anon_inner_length; 7 | mod binary_expression_complexity; 8 | mod class_data_abstraction_coupling; 9 | mod cyclomatic_complexity; 10 | mod file_length; 11 | mod function_length; 12 | mod missing_switch_default; 13 | mod parameter_number; 14 | 15 | #[derive(Debug, Serialize)] 16 | #[serde(rename_all = "camelCase")] 17 | pub enum StatLevel { 18 | Module, 19 | Class, 20 | Function, 21 | Statement, 22 | Item, 23 | } 24 | 25 | impl Default for StatLevel { 26 | fn default() -> Self { 27 | Self::Module 28 | } 29 | } 30 | 31 | #[derive(Debug, Serialize)] 32 | #[serde(rename_all = "camelCase")] 33 | pub struct StatRecord { 34 | pub metric: String, 35 | pub metric_short: String, 36 | pub level: StatLevel, 37 | pub count: u32, 38 | pub threshold: u32, 39 | pub score: f64, 40 | } 41 | 42 | impl Default for StatRecord { 43 | fn default() -> Self { 44 | Self { 45 | metric: "".to_string(), 46 | metric_short: "".to_string(), 47 | level: StatLevel::default(), 48 | count: 0, 49 | threshold: 0, 50 | score: 0.0, 51 | } 52 | } 53 | } 54 | 55 | pub trait Stat: std::fmt::Debug + Send + Sync { 56 | fn new() -> Arc 57 | where 58 | Self: Sized; 59 | 60 | #[allow(dead_code)] 61 | fn code(&self) -> &'static str; 62 | 63 | #[allow(dead_code)] 64 | fn short_code(&self) -> &'static str; 65 | 66 | fn stat<'a>(&self, context: &mut Context<'a>, maybe_threshold: Option); 67 | } 68 | 69 | fn get_stats() -> Vec> { 70 | vec![ 71 | anon_inner_length::AnonInnerLength::new(), 72 | binary_expression_complexity::BinaryExpressionComplexity::new(), 73 | class_data_abstraction_coupling::ClassDataAbstractionCoupling::new(), 74 | cyclomatic_complexity::CyclomaticComplexity::new(), 75 | file_length::FileLength::new(), 76 | function_length::FunctionLength::new(), 77 | missing_switch_default::MissingSwitchDefault::new(), 78 | parameter_number::ParameterNumber::new(), 79 | ] 80 | } 81 | 82 | pub fn get_all_stats() -> Vec> { 83 | get_stats() 84 | } 85 | -------------------------------------------------------------------------------- /src/utils/log.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module 3 | */ 4 | 5 | import { colors } from "@cliffy/ansi/colors"; 6 | 7 | export class Log { 8 | #log = console.log.bind(console); 9 | #depth = 0; 10 | #segmenter: Intl.Segmenter; 11 | 12 | #colorizeFirstWord(text: string, fn: (s: string) => string) { 13 | const [first, ...rest] = this.#segmenter.segment(text); 14 | return [fn(first.segment), ...rest.map(({ segment }) => segment)].join(""); 15 | } 16 | 17 | #indent(value: unknown): string { 18 | const str = typeof value === "string" 19 | ? value 20 | : Deno.inspect(value, { colors: true }); 21 | if (this.#depth === 0) { 22 | return str; 23 | } 24 | return str 25 | .split("\n") 26 | .map((line) => `${" ".repeat(this.#depth)}${line}`) 27 | .join("\n"); 28 | } 29 | 30 | get depth(): number { 31 | return this.#depth; 32 | } 33 | 34 | constructor(locale = "en") { 35 | this.#segmenter = new Intl.Segmenter(locale, { granularity: "word" }); 36 | } 37 | 38 | error(text: string, ...other: unknown[]): this { 39 | this.#log( 40 | this.#indent(this.#colorizeFirstWord(text, colors.brightRed)), 41 | ...other, 42 | ); 43 | return this; 44 | } 45 | 46 | group(data?: unknown, ...rest: unknown[]): this { 47 | if (data) { 48 | this.#log(this.#indent(data), ...rest); 49 | } 50 | this.#depth++; 51 | return this; 52 | } 53 | 54 | groupEnd(): this { 55 | this.#depth = this.#depth <= 0 ? 0 : this.#depth - 1; 56 | return this; 57 | } 58 | 59 | kv(key: string, value: unknown): this { 60 | this.#log(this.#indent(`${colors.green(key)}:`), value); 61 | return this; 62 | } 63 | 64 | log(data?: unknown, ...rest: unknown[]): this { 65 | this.#log(this.#indent(data), ...rest); 66 | return this; 67 | } 68 | 69 | light(text: string, ...other: unknown[]): this { 70 | this.#log(this.#indent(colors.dim(text)), ...other); 71 | return this; 72 | } 73 | 74 | step(text: string, ...other: unknown[]): this { 75 | this.#log( 76 | this.#indent(this.#colorizeFirstWord(text, colors.brightGreen)), 77 | ...other, 78 | ); 79 | return this; 80 | } 81 | 82 | warn(text: string, ...other: unknown[]): this { 83 | this.#log( 84 | this.#indent(this.#colorizeFirstWord(text, colors.brightYellow)), 85 | ...other, 86 | ); 87 | return this; 88 | } 89 | } 90 | 91 | export const log = new Log(); 92 | -------------------------------------------------------------------------------- /src/stats/missing_switch_default.rs: -------------------------------------------------------------------------------- 1 | use deno_ast::swc::ast; 2 | use deno_ast::swc::visit::noop_visit_type; 3 | use deno_ast::swc::visit::Visit; 4 | use deno_ast::swc::visit::VisitWith; 5 | use std::sync::Arc; 6 | 7 | use super::Stat; 8 | use super::StatLevel; 9 | use super::StatRecord; 10 | use crate::context::Context; 11 | 12 | const CODE: &str = "missing-switch-default"; 13 | const SHORT_CODE: &str = "MSD"; 14 | 15 | #[derive(Debug)] 16 | pub struct MissingSwitchDefault; 17 | 18 | impl Stat for MissingSwitchDefault { 19 | fn new() -> Arc { 20 | Arc::new(Self) 21 | } 22 | 23 | fn code(&self) -> &'static str { 24 | CODE 25 | } 26 | 27 | fn short_code(&self) -> &'static str { 28 | SHORT_CODE 29 | } 30 | 31 | fn stat<'view>( 32 | &self, 33 | context: &mut Context<'view>, 34 | _maybe_threshold: Option, 35 | ) { 36 | let mut collector = MissingSwitchDefaultCollector::new(); 37 | context.parsed_source.module().visit_with(&mut collector); 38 | context.add_stat(StatRecord { 39 | metric: CODE.to_string(), 40 | metric_short: SHORT_CODE.to_string(), 41 | level: StatLevel::Statement, 42 | threshold: 1, 43 | count: collector.count, 44 | score: collector.score, 45 | }); 46 | } 47 | } 48 | 49 | struct MissingSwitchDefaultCollector { 50 | count: u32, 51 | score: f64, 52 | } 53 | 54 | impl MissingSwitchDefaultCollector { 55 | pub fn new() -> Self { 56 | Self { 57 | count: 0, 58 | score: 0.0, 59 | } 60 | } 61 | } 62 | 63 | impl Visit for MissingSwitchDefaultCollector { 64 | noop_visit_type!(); 65 | 66 | fn visit_switch_stmt(&mut self, node: &ast::SwitchStmt) { 67 | self.count += 1; 68 | let has_default = node.cases.iter().any(|case| case.test.is_none()); 69 | if !has_default { 70 | self.score += 1.0; 71 | } 72 | } 73 | } 74 | 75 | #[cfg(test)] 76 | mod tests { 77 | use super::*; 78 | use url::Url; 79 | 80 | #[test] 81 | fn counts_missing_default() { 82 | let source = r#"export function foo(value: string) { 83 | switch (value) { 84 | case "foo": 85 | break; 86 | case "bar": 87 | break; 88 | default: 89 | console.log("?"); 90 | } 91 | 92 | switch (value) { 93 | case "foo": 94 | break; 95 | } 96 | }"#; 97 | 98 | let parsed_source = deno_ast::parse_module(deno_ast::ParseParams { 99 | specifier: Url::parse("file://test/a.ts").unwrap(), 100 | text: source.into(), 101 | media_type: deno_ast::MediaType::TypeScript, 102 | capture_tokens: true, 103 | scope_analysis: false, 104 | maybe_syntax: None, 105 | }) 106 | .unwrap(); 107 | 108 | let mut collector = MissingSwitchDefaultCollector::new(); 109 | parsed_source.module().visit_with(&mut collector); 110 | assert_eq!(collector.count, 2); 111 | assert_eq!(collector.score, 1.0); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/commands/stat.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module 3 | */ 4 | 5 | import { colors } from "@cliffy/ansi/colors"; 6 | import { Command } from "@cliffy/command"; 7 | import { type Cell, Row, Table } from "@cliffy/table"; 8 | import { wait } from "@denosaurs/wait"; 9 | import { common } from "@std/path"; 10 | 11 | import { log } from "../utils/log.ts"; 12 | import { asURL, instantiate, stats } from "../../mod.ts"; 13 | 14 | export default new Command() 15 | .arguments("") 16 | .description("Analyze source outputting code toxicity stats.") 17 | .action(async (_options, source) => { 18 | performance.mark("stats-start"); 19 | log.step(`Analyzing code starting at "${source}"...`); 20 | let url: URL; 21 | try { 22 | url = new URL(source); 23 | } catch { 24 | url = asURL(source); 25 | } 26 | const spinner = wait("Analyzing...").start(); 27 | await instantiate(); 28 | const results = await stats(url); 29 | const measure = performance.measure("stats-start"); 30 | spinner.succeed(`Done in ${measure.duration.toFixed(2)}ms.`); 31 | const commonPath = common([...results.keys()]); 32 | const values = new Map< 33 | string, 34 | { metricShort: string; count: number; score: number } 35 | >(); 36 | const rows = new Map< 37 | string, 38 | { label: string; total: number; items: Map } 39 | >(); 40 | for (const [path, records] of results) { 41 | let total = 0; 42 | const label = path.replace(commonPath, ""); 43 | const items = new Map(); 44 | for (const { metric, metricShort, count, score } of records) { 45 | if (!values.has(metric)) { 46 | values.set(metric, { metricShort, count: 0, score: 0 }); 47 | } 48 | const value = values.get(metric)!; 49 | value.count += count; 50 | value.score += score; 51 | total += score; 52 | items.set(metric, score); 53 | } 54 | rows.set(path, { label, total, items }); 55 | } 56 | const statHeader = [...values.values()] 57 | .map(({ metricShort }) => metricShort); 58 | const body: (string | number | Cell | undefined)[][] = []; 59 | for (const { label, total, items } of rows.values()) { 60 | const row: (string | number | Cell | undefined)[] = [ 61 | label, 62 | total ? total.toFixed(2) : undefined, 63 | ]; 64 | for (const metric of values.keys()) { 65 | const value = items.get(metric); 66 | row.push(value ? value.toFixed(2) : undefined); 67 | } 68 | body.push(row); 69 | } 70 | const counts: (string | number | undefined)[] = [ 71 | colors.italic("Counts"), 72 | undefined, 73 | ]; 74 | const scores = [colors.bold("Total"), undefined]; 75 | for (const { count, score } of values.values()) { 76 | counts.push(count ? count : undefined); 77 | scores.push(score ? score.toFixed(2) : undefined); 78 | } 79 | body.push(counts, scores); 80 | new Table() 81 | .header(Row.from(["Path", "Score", ...statHeader]).border(true)) 82 | .body(body) 83 | .render(); 84 | }); 85 | -------------------------------------------------------------------------------- /src/resolve.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | #[cfg(not(target_arch = "wasm32"))] 3 | use std::path::Path; 4 | #[cfg(not(target_arch = "wasm32"))] 5 | use std::path::PathBuf; 6 | use url::Url; 7 | 8 | /// Normalize all intermediate components of the path (ie. remove "./" and "../" 9 | /// components). 10 | /// 11 | /// Similar to `fs::canonicalize()` but doesn't resolve symlinks. 12 | /// 13 | /// Taken from Cargo 14 | /// https://github.com/rust-lang/cargo/blob/af307a38c20a753ec60f0ad18be5abed3db3c9ac/src/cargo/util/paths.rs#L60-L85 15 | #[cfg(not(target_arch = "wasm32"))] 16 | fn normalize_path>(path: P) -> PathBuf { 17 | use std::path::Component; 18 | 19 | let mut components = path.as_ref().components().peekable(); 20 | let mut ret = 21 | if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { 22 | components.next(); 23 | PathBuf::from(c.as_os_str()) 24 | } else { 25 | PathBuf::new() 26 | }; 27 | 28 | for component in components { 29 | match component { 30 | Component::Prefix(..) => unreachable!(), 31 | Component::RootDir => { 32 | ret.push(component.as_os_str()); 33 | } 34 | Component::CurDir => {} 35 | Component::ParentDir => { 36 | ret.pop(); 37 | } 38 | Component::Normal(c) => { 39 | ret.push(c); 40 | } 41 | } 42 | } 43 | ret 44 | } 45 | 46 | /// Returns true if the input string starts with a sequence of characters 47 | /// that could be a valid URI scheme, like 'https:', 'git+ssh:' or 'data:'. 48 | /// 49 | /// According to RFC 3986 (https://tools.ietf.org/html/rfc3986#section-3.1), 50 | /// a valid scheme has the following format: 51 | /// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) 52 | /// 53 | /// We additionally require the scheme to be at least 2 characters long, 54 | /// because otherwise a windows path like c:/foo would be treated as a URL, 55 | /// while no schemes with a one-letter name actually exist. 56 | fn specifier_has_uri_scheme(specifier: &str) -> bool { 57 | let mut chars = specifier.chars(); 58 | let mut len = 0usize; 59 | // The first character must be a letter. 60 | match chars.next() { 61 | Some(c) if c.is_ascii_alphabetic() => len += 1, 62 | _ => return false, 63 | } 64 | // Second and following characters must be either a letter, number, 65 | // plus sign, minus sign, or dot. 66 | loop { 67 | match chars.next() { 68 | Some(c) if c.is_ascii_alphanumeric() || "+-.".contains(c) => len += 1, 69 | Some(':') if len >= 2 => return true, 70 | _ => return false, 71 | } 72 | } 73 | } 74 | 75 | #[cfg(not(target_arch = "wasm32"))] 76 | fn resolve_path(path_str: &str) -> Result { 77 | use anyhow::anyhow; 78 | 79 | let path = std::env::current_dir().unwrap().join(path_str); 80 | let path = normalize_path(&path); 81 | Url::from_file_path(path).map_err(|_| anyhow!("Invalid URL.")) 82 | } 83 | 84 | #[cfg(target_arch = "wasm32")] 85 | fn resolve_path(path_str: &str) -> Result { 86 | Url::parse(&format!("file://{}", path_str)).map_err(|err| err.into()) 87 | } 88 | 89 | pub(crate) fn resolve_url_or_path(specifier: &str) -> Result { 90 | if specifier_has_uri_scheme(specifier) { 91 | Url::parse(specifier).map_err(|err| err.into()) 92 | } else { 93 | resolve_path(specifier) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/stats/class_data_abstraction_coupling.rs: -------------------------------------------------------------------------------- 1 | use deno_ast::swc::ast; 2 | use deno_ast::swc::visit::noop_visit_type; 3 | use deno_ast::swc::visit::Visit; 4 | use deno_ast::swc::visit::VisitWith; 5 | use std::sync::Arc; 6 | 7 | use super::Stat; 8 | use super::StatLevel; 9 | use super::StatRecord; 10 | use crate::context::Context; 11 | 12 | const CODE: &str = "class-data-abstraction-coupling"; 13 | const SHORT_CODE: &str = "CDAC"; 14 | 15 | #[derive(Debug)] 16 | pub struct ClassDataAbstractionCoupling; 17 | 18 | impl Stat for ClassDataAbstractionCoupling { 19 | fn new() -> Arc { 20 | Arc::new(Self) 21 | } 22 | 23 | fn code(&self) -> &'static str { 24 | CODE 25 | } 26 | 27 | fn short_code(&self) -> &'static str { 28 | SHORT_CODE 29 | } 30 | 31 | fn stat<'view>( 32 | &self, 33 | context: &mut Context<'view>, 34 | maybe_threshold: Option, 35 | ) { 36 | let threshold = maybe_threshold.unwrap_or(10); 37 | let mut collector = ClassDataAbstractionCouplingCollector::new(threshold); 38 | context.parsed_source.module().visit_with(&mut collector); 39 | context.add_stat(StatRecord { 40 | metric: CODE.to_string(), 41 | metric_short: SHORT_CODE.to_string(), 42 | level: StatLevel::Class, 43 | threshold, 44 | count: collector.count, 45 | score: collector.score, 46 | }); 47 | } 48 | } 49 | 50 | struct ClassDataAbstractionCouplingCollector { 51 | count: u32, 52 | score: f64, 53 | threshold: u32, 54 | } 55 | 56 | impl ClassDataAbstractionCouplingCollector { 57 | pub fn new(threshold: u32) -> Self { 58 | Self { 59 | count: 0, 60 | score: 0.0, 61 | threshold, 62 | } 63 | } 64 | } 65 | 66 | impl Visit for ClassDataAbstractionCouplingCollector { 67 | noop_visit_type!(); 68 | 69 | fn visit_class(&mut self, node: &ast::Class) { 70 | self.count += 1; 71 | let mut collector = NewExpressionCollector::new(); 72 | node.visit_with(&mut collector); 73 | let new_expr_count = collector.count(); 74 | if new_expr_count >= self.threshold { 75 | self.score += new_expr_count as f64 / self.threshold as f64; 76 | } 77 | } 78 | } 79 | 80 | struct NewExpressionCollector(u32); 81 | 82 | impl NewExpressionCollector { 83 | pub fn new() -> Self { 84 | Self(0) 85 | } 86 | 87 | pub fn count(&self) -> u32 { 88 | self.0 89 | } 90 | } 91 | 92 | impl Visit for NewExpressionCollector { 93 | noop_visit_type!(); 94 | 95 | fn visit_new_expr(&mut self, _node: &ast::NewExpr) { 96 | self.0 += 1; 97 | } 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use super::*; 103 | use url::Url; 104 | 105 | #[test] 106 | fn collector() { 107 | let source = r#" 108 | export class Foo { 109 | foo = "foo"; 110 | } 111 | 112 | export class Bar { 113 | foo = new Foo(); 114 | foo1 = new Foo(); 115 | foo2 = new Foo(); 116 | foo3 = new Foo(); 117 | foo4 = new Foo(); 118 | foo5 = new Foo(); 119 | foo6 = new Foo(); 120 | foo7 = new Foo(); 121 | foo8 = new Foo(); 122 | getFoo(): Foo { 123 | return new Foo(); 124 | } 125 | } 126 | "#; 127 | 128 | let parsed_source = deno_ast::parse_module(deno_ast::ParseParams { 129 | specifier: Url::parse("file://test/a.ts").unwrap(), 130 | text: source.into(), 131 | media_type: deno_ast::MediaType::TypeScript, 132 | capture_tokens: true, 133 | scope_analysis: false, 134 | maybe_syntax: None, 135 | }) 136 | .unwrap(); 137 | 138 | let mut collector = ClassDataAbstractionCouplingCollector::new(10); 139 | parsed_source.module().visit_with(&mut collector); 140 | assert_eq!(collector.count, 2); 141 | assert_eq!(collector.score, 1.0); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/stats/binary_expression_complexity.rs: -------------------------------------------------------------------------------- 1 | use deno_ast::swc::ast; 2 | use deno_ast::swc::visit::noop_visit_type; 3 | use deno_ast::swc::visit::Visit; 4 | use deno_ast::swc::visit::VisitWith; 5 | use deno_ast::view; 6 | use std::sync::Arc; 7 | 8 | use super::Stat; 9 | use super::StatLevel; 10 | use super::StatRecord; 11 | use crate::context::Context; 12 | use crate::walker::Traverse; 13 | use crate::walker::Walker; 14 | 15 | const CODE: &str = "binary-expression-complexity"; 16 | const SHORT_CODE: &str = "BEC"; 17 | 18 | #[derive(Debug)] 19 | pub struct BinaryExpressionComplexity; 20 | 21 | impl Stat for BinaryExpressionComplexity { 22 | fn new() -> Arc { 23 | Arc::new(Self) 24 | } 25 | 26 | fn code(&self) -> &'static str { 27 | CODE 28 | } 29 | 30 | fn short_code(&self) -> &'static str { 31 | SHORT_CODE 32 | } 33 | 34 | fn stat<'view>( 35 | &self, 36 | ctx: &mut Context<'view>, 37 | maybe_threshold: Option, 38 | ) { 39 | let threshold = maybe_threshold.unwrap_or(3); 40 | let mut walker = BinaryExpressionComplexityWalker::new(threshold); 41 | walker.traverse(ctx.program, ctx); 42 | ctx.add_stat(StatRecord { 43 | metric: CODE.to_string(), 44 | metric_short: SHORT_CODE.to_string(), 45 | level: StatLevel::Statement, 46 | threshold, 47 | count: walker.count, 48 | score: walker.score, 49 | }); 50 | } 51 | } 52 | 53 | pub struct BinaryExpressionComplexityWalker { 54 | count: u32, 55 | score: f64, 56 | threshold: u32, 57 | } 58 | 59 | impl BinaryExpressionComplexityWalker { 60 | pub fn new(threshold: u32) -> Self { 61 | Self { 62 | count: 0, 63 | score: 0.0, 64 | threshold, 65 | } 66 | } 67 | } 68 | 69 | impl Walker for BinaryExpressionComplexityWalker { 70 | fn bin_expr(&mut self, node: &view::BinExpr, ctx: &mut Context) { 71 | self.count += 1; 72 | let mut counter = BinaryExpressionComplexityCounter::new(); 73 | node.inner.visit_children_with(&mut counter); 74 | let complexity = counter.count(); 75 | if complexity >= self.threshold { 76 | self.score += complexity as f64 / self.threshold as f64; 77 | } 78 | ctx.traversal.skip(); 79 | } 80 | } 81 | 82 | pub struct BinaryExpressionComplexityCounter(u32); 83 | 84 | impl BinaryExpressionComplexityCounter { 85 | pub fn new() -> Self { 86 | Self(1) 87 | } 88 | 89 | pub fn count(&self) -> u32 { 90 | self.0 91 | } 92 | } 93 | 94 | impl Visit for BinaryExpressionComplexityCounter { 95 | noop_visit_type!(); 96 | 97 | fn visit_bin_expr(&mut self, node: &ast::BinExpr) { 98 | self.0 += 1; 99 | node.visit_children_with(self); 100 | } 101 | } 102 | 103 | #[cfg(test)] 104 | mod tests { 105 | use super::*; 106 | use url::Url; 107 | 108 | #[test] 109 | fn walker_counts_properly() { 110 | let source = r#"const bar = 1; 111 | 112 | export const foo = bar || "foo"; 113 | 114 | if (bar && foo) { 115 | console.log(bar || foo); 116 | } 117 | 118 | if ((bar && foo && true) || "") { 119 | console.log("bar"); 120 | }"#; 121 | 122 | let parsed_source = deno_ast::parse_module(deno_ast::ParseParams { 123 | specifier: Url::parse("file://test/a.ts").unwrap(), 124 | text: source.into(), 125 | media_type: deno_ast::MediaType::TypeScript, 126 | capture_tokens: true, 127 | scope_analysis: false, 128 | maybe_syntax: None, 129 | }) 130 | .unwrap(); 131 | 132 | let mut walker = BinaryExpressionComplexityWalker::new(3); 133 | 134 | parsed_source.with_view(|program| { 135 | let mut ctx = Context::new(parsed_source.clone(), program); 136 | walker.traverse(ctx.program, &mut ctx); 137 | assert_eq!(walker.count, 4); 138 | assert_eq!(walker.score, 1.0); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nocuous 2 | 3 | ![CI](https://github.com/h-o-t/nocuous/workflows/ci/badge.svg) 4 | [![jsr.io/@higher-order-testing/nocuous](https://jsr.io/badges/@higher-order-testing/nocuous)](https://jsr.io/@higher-order-testing/nocuous) 5 | [![jsr.io/@higher-order-testing/nocuous score](https://jsr.io/badges/@higher-order-testing/nocuous/score)](https://jsr.io/@higher-order-testing/nocuous) 6 | 7 | A static code analysis tool for JavaScript and TypeScript. 8 | 9 | ## Installing the CLI 10 | 11 | If you want to install the CLI, you would need to have Deno 12 | [installed first](https://docs.deno.com/runtime/getting_started/installation/) 13 | and then on the command line, you would want to run the following command: 14 | 15 | ```shell 16 | $ deno install --name nocuous --allow-read --allow-net -f jsr:@higher-order-testing/nocuous/cli 17 | ``` 18 | 19 | You can also "pin" to a specific version by using `nocuous@{version}` instead, 20 | for example `jsr:@higher-order-testing/nocuous@1.1.0/cli`. 21 | 22 | The CLI comes with integrated help which can be accessed via the `--help` flag. 23 | 24 | ## Using the API 25 | 26 | If you want to incorporate the API into an application, you need to import it 27 | into your code. For example the following will analyze the Deno std assertion 28 | library and its dependencies resolving with a map of statistics: 29 | 30 | ```ts 31 | import { instantiate, stats } from "jsr:@higher-order-testing/nocuous"; 32 | 33 | await instantiate(); 34 | 35 | const results = await stats(new URL("https://jsr.io/@std/assert/1.0.6/mod.ts")); 36 | 37 | console.log(results); 38 | ``` 39 | 40 | ## Architecture 41 | 42 | The tool uses [swc](https://swc.rs/) as a Rust library to parse code and then 43 | run analysis over the parsed code. It is then compiled to Web Assembly and 44 | exposed as an all-in-one API. Code is loaded via the JavaScript runtime and a 45 | resolver can be provided to allow for custom resolution logic. 46 | 47 | ## Background 48 | 49 | The statistics collected around code toxicity are based directly on Erik 50 | Dörnenburg's article 51 | [How toxic is your code?](https://erik.doernenburg.com/2008/11/how-toxic-is-your-code/). 52 | 53 | The default metrics are based on what is suggested in the article. When applying 54 | to TypeScript/JavaScript there are some adaptation that is required: 55 | 56 | | Metric | Table Label | Description | Default Threshold | 57 | | ------------------------------- | ----------- | ----------------------------------------------------------------------------------------------- | ----------------- | 58 | | File length | L | The number of lines in a file. | 500 | 59 | | Class data abstraction coupling | CDAC | The number of instances of other classes that are "new"ed in a given class. | 10 | 60 | | Anon Inner Length | AIL | Class expressions of arrow functions length in number of lines. | 35 | 61 | | Function Length | FL | The number of statements in a function declaration, function expression, or method declaration. | 30 | 62 | | Parameter Number | P | The number of parameters for a function or method | 6 | 63 | | Cyclomatic Complexity | CC | The cyclomatic complexity for a function or method | 10 | 64 | | Binary Expression Complexity | BEC | How complex a binary expression is (e.g. how many `&&` and ` | | 65 | | Missing Switch Default | MSD | Any `switch` statements that are missing the `default` case. | 1 | 66 | 67 | --- 68 | 69 | Copyright 2019 - 2024 Kitson P. Kelly. MIT License. 70 | -------------------------------------------------------------------------------- /src/stats/parameter_number.rs: -------------------------------------------------------------------------------- 1 | use deno_ast::swc::ast; 2 | use deno_ast::swc::visit::noop_visit_type; 3 | use deno_ast::swc::visit::Visit; 4 | use deno_ast::swc::visit::VisitWith; 5 | use std::sync::Arc; 6 | 7 | use super::Stat; 8 | use super::StatLevel; 9 | use super::StatRecord; 10 | use crate::context::Context; 11 | 12 | const CODE: &str = "parameter-number"; 13 | const SHORT_CODE: &str = "P"; 14 | 15 | #[derive(Debug)] 16 | pub struct ParameterNumber; 17 | 18 | impl Stat for ParameterNumber { 19 | fn new() -> Arc { 20 | Arc::new(Self) 21 | } 22 | 23 | fn code(&self) -> &'static str { 24 | CODE 25 | } 26 | 27 | fn short_code(&self) -> &'static str { 28 | SHORT_CODE 29 | } 30 | 31 | fn stat<'view>( 32 | &self, 33 | ctx: &mut Context<'view>, 34 | maybe_threshold: Option, 35 | ) { 36 | let threshold = maybe_threshold.unwrap_or(6); 37 | let mut collector = ParameterNumberCollector::new(threshold); 38 | ctx.parsed_source.module().visit_with(&mut collector); 39 | ctx.add_stat(StatRecord { 40 | metric: CODE.to_string(), 41 | metric_short: SHORT_CODE.to_string(), 42 | level: StatLevel::Function, 43 | threshold, 44 | count: collector.count, 45 | score: collector.score, 46 | }); 47 | } 48 | } 49 | 50 | struct ParameterNumberCollector { 51 | count: u32, 52 | score: f64, 53 | threshold: u32, 54 | } 55 | 56 | impl ParameterNumberCollector { 57 | pub fn new(threshold: u32) -> Self { 58 | Self { 59 | count: 0, 60 | score: 0.0, 61 | threshold, 62 | } 63 | } 64 | } 65 | 66 | impl Visit for ParameterNumberCollector { 67 | noop_visit_type!(); 68 | 69 | fn visit_arrow_expr(&mut self, node: &ast::ArrowExpr) { 70 | self.count += 1; 71 | let param_count = node.params.len(); 72 | if param_count as u32 >= self.threshold { 73 | self.score += param_count as f64 / self.threshold as f64; 74 | } 75 | } 76 | 77 | fn visit_class_method(&mut self, node: &ast::ClassMethod) { 78 | self.count += 1; 79 | let param_count = node.function.params.len(); 80 | if param_count as u32 >= self.threshold { 81 | self.score += param_count as f64 / self.threshold as f64; 82 | } 83 | } 84 | 85 | fn visit_fn_decl(&mut self, node: &ast::FnDecl) { 86 | self.count += 1; 87 | let param_count = node.function.params.len(); 88 | if param_count as u32 >= self.threshold { 89 | self.score += param_count as f64 / self.threshold as f64; 90 | } 91 | } 92 | 93 | fn visit_fn_expr(&mut self, node: &ast::FnExpr) { 94 | self.count += 1; 95 | let param_count = node.function.params.len(); 96 | if param_count as u32 >= self.threshold { 97 | self.score += param_count as f64 / self.threshold as f64; 98 | } 99 | } 100 | 101 | fn visit_method_prop(&mut self, node: &ast::MethodProp) { 102 | self.count += 1; 103 | let param_count = node.function.params.len(); 104 | if param_count as u32 >= self.threshold { 105 | self.score += param_count as f64 / self.threshold as f64; 106 | } 107 | } 108 | 109 | fn visit_private_method(&mut self, node: &ast::PrivateMethod) { 110 | self.count += 1; 111 | let param_count = node.function.params.len(); 112 | if param_count as u32 >= self.threshold { 113 | self.score += param_count as f64 / self.threshold as f64; 114 | } 115 | } 116 | } 117 | 118 | #[cfg(test)] 119 | mod tests { 120 | use super::*; 121 | use url::Url; 122 | 123 | #[test] 124 | fn collector_works() { 125 | let source = r#"class A { 126 | a(a: string, b: string) {} 127 | #b(a: string, b: string, c: string, d: string, e: string, f: string) {} 128 | } 129 | 130 | function d(a: string, b: string) {} 131 | 132 | function e(a: string, b: string, c: string, d: string, e: string, f: string) {} 133 | "#; 134 | 135 | let parsed_source = deno_ast::parse_module(deno_ast::ParseParams { 136 | specifier: Url::parse("file://test/a.ts").unwrap(), 137 | text: source.into(), 138 | media_type: deno_ast::MediaType::TypeScript, 139 | capture_tokens: true, 140 | scope_analysis: false, 141 | maybe_syntax: None, 142 | }) 143 | .unwrap(); 144 | 145 | let mut collector = ParameterNumberCollector::new(6); 146 | parsed_source.module().visit_with(&mut collector); 147 | assert_eq!(collector.count, 4); 148 | assert_eq!(collector.score, 2.0); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A static analysis tool for JavaScript and TypeScript that provides code 3 | * toxicity information. 4 | * 5 | * ### Example 6 | * 7 | * Fetches the `@std/assert` library for Deno and its dependencies and returns 8 | * a map of the code toxicity statistics. 9 | * 10 | * ```ts 11 | * import { instantiate, stats } from "jsr:@higher-order-testing/nocuous"; 12 | * 13 | * await instantiate(); 14 | * 15 | * const results = await stats(new URL("https://jsr.io/@std/assert/1.0.6/mod.ts")); 16 | * 17 | * console.log(results); 18 | * ``` 19 | * 20 | * @module 21 | */ 22 | 23 | import { isAbsolute, join, toFileUrl } from "@std/path"; 24 | import * as wasm from "./lib/nocuous.generated.js"; 25 | 26 | interface InstantiationOptions { 27 | url?: URL; 28 | decompress?: (compressed: Uint8Array) => Uint8Array; 29 | } 30 | 31 | /** The level in a {@linkcode StatRecord} that the statistic pertains to. */ 32 | export enum StatLevel { 33 | Module = "module", 34 | Class = "class", 35 | Function = "function", 36 | Statement = "statement", 37 | Item = "item", 38 | } 39 | 40 | /** The interface representing a return value from {@linkcode stats}. */ 41 | export interface StatRecord { 42 | /** The name of the metric. */ 43 | metric: string; 44 | /** The short name of the metric. This can be used for display in a table 45 | * for example. */ 46 | metricShort: string; 47 | /** At what level does the statistic apply to. */ 48 | level: StatLevel; 49 | /** The count of the number of items detected in the file/module. */ 50 | count: number; 51 | /** The threshold used for determining the score. */ 52 | threshold: number; 53 | /** How "toxic" where if any item exceeded the threshold, the score would be 54 | * at least 1, where the value is the statistic divided by the threshold. */ 55 | score: number; 56 | } 57 | 58 | /** Options which can be set when calling {@linkcode stats}. */ 59 | interface StatsOptions { 60 | /** Override the default load behavior, which uses {@linkcode fetch} to 61 | * retrieve local or remote resources. */ 62 | load?: ( 63 | specifier: string, 64 | ) => Promise<[content: string | undefined, contentType: string | undefined]>; 65 | /** Override the default resolution behavior, which is a literal resolution 66 | * behavior used by Deno and web browsers. */ 67 | resolve?: (specifier: string, referrer: string) => Promise; 68 | } 69 | 70 | async function defaultLoad( 71 | specifier: string, 72 | ): Promise<[content: string | undefined, contentType: string | undefined]> { 73 | const res = await fetch(specifier); 74 | if (res.status === 200) { 75 | const contentType = res.headers.get("content-type") ?? undefined; 76 | return [await res.text(), contentType]; 77 | } 78 | return [undefined, undefined]; 79 | } 80 | 81 | /** Asynchronously instantiate the Wasm module. This needs to occur before using 82 | * other exported functions. */ 83 | export function instantiate( 84 | options?: InstantiationOptions, 85 | ): Promise<{ stats: typeof stats }> { 86 | return wasm.instantiate(options).then(() => ({ stats })); 87 | } 88 | 89 | /** Given a path and an optional base to use for a relative path, return a file 90 | * {@linkcode URL} for the path. 91 | * 92 | * `base` defaults to `Deno.cwd()`. If `base` is not absolute it will throw. 93 | */ 94 | export function asURL(path: string, base?: string): URL; 95 | /** Given an array of paths and an optional base to use for relative paths, 96 | * return an array of file {@linkcode URL}s for the paths. 97 | * 98 | * `base` defaults to `Deno.cwd()`. If `base` is not absolute it will throw. 99 | */ 100 | export function asURL(paths: string[], base?: string): URL[]; 101 | export function asURL( 102 | paths: string | string[], 103 | base = Deno.cwd(), 104 | ): URL | URL[] { 105 | if (!isAbsolute(base)) { 106 | throw new TypeError(`The base of "${base}" must be absolute.`); 107 | } 108 | const inputIsArray = Array.isArray(paths); 109 | paths = Array.isArray(paths) ? paths : [paths]; 110 | const urls = paths.map((path) => 111 | toFileUrl(isAbsolute(path) ? path : join(base, path)) 112 | ); 113 | return inputIsArray ? urls : urls[0]; 114 | } 115 | 116 | /** Given a set of URLs, perform a statistical analysis on the roots and their 117 | * dependencies, resolving with a {@linkcode Map} where the key is the string 118 | * URL of the file and the value is a {@linkcode StatRecord}. */ 119 | export function stats( 120 | roots: URL | URL[], 121 | options: StatsOptions = {}, 122 | ): Promise> { 123 | const { load = defaultLoad, resolve } = options; 124 | const targets = Array.isArray(roots) 125 | ? roots.map((url) => url.toString()) 126 | : [roots.toString()]; 127 | return wasm.stats(targets, load, resolve); 128 | } 129 | -------------------------------------------------------------------------------- /src/stats/anon_inner_length.rs: -------------------------------------------------------------------------------- 1 | use deno_ast::swc::ast; 2 | use deno_ast::swc::visit::noop_visit_type; 3 | use deno_ast::swc::visit::Visit; 4 | use deno_ast::swc::visit::VisitWith; 5 | use deno_ast::SourceRangedForSpanned; 6 | use deno_ast::SourceTextInfo; 7 | use std::sync::Arc; 8 | 9 | use super::Stat; 10 | use super::StatLevel; 11 | use super::StatRecord; 12 | use crate::analysis::lines_of_code; 13 | use crate::context::Context; 14 | 15 | const CODE: &str = "anon-inner-length"; 16 | const SHORT_CODE: &str = "AIL"; 17 | 18 | #[derive(Debug)] 19 | pub struct AnonInnerLength; 20 | 21 | impl Stat for AnonInnerLength { 22 | fn new() -> Arc { 23 | Arc::new(Self) 24 | } 25 | 26 | fn code(&self) -> &'static str { 27 | CODE 28 | } 29 | 30 | fn short_code(&self) -> &'static str { 31 | SHORT_CODE 32 | } 33 | 34 | fn stat<'view>( 35 | &self, 36 | context: &mut Context<'view>, 37 | maybe_threshold: Option, 38 | ) { 39 | let threshold = maybe_threshold.unwrap_or(35); 40 | let mut collector = AnonInnerLengthCollector::new( 41 | threshold, 42 | context.parsed_source.text_info_lazy(), 43 | ); 44 | context.parsed_source.module().visit_with(&mut collector); 45 | context.add_stat(StatRecord { 46 | metric: CODE.to_string(), 47 | metric_short: SHORT_CODE.to_string(), 48 | level: StatLevel::Item, 49 | threshold, 50 | count: collector.count, 51 | score: collector.score, 52 | }); 53 | } 54 | } 55 | 56 | struct AnonInnerLengthCollector<'view> { 57 | count: u32, 58 | score: f64, 59 | text_info: &'view SourceTextInfo, 60 | threshold: u32, 61 | } 62 | 63 | impl<'view> AnonInnerLengthCollector<'view> { 64 | pub fn new(threshold: u32, text_info: &'view SourceTextInfo) -> Self { 65 | Self { 66 | count: 0, 67 | score: 0.0, 68 | text_info, 69 | threshold, 70 | } 71 | } 72 | } 73 | 74 | impl<'view> Visit for AnonInnerLengthCollector<'view> { 75 | noop_visit_type!(); 76 | 77 | fn visit_arrow_expr(&mut self, node: &ast::ArrowExpr) { 78 | self.count += 1; 79 | let code = self.text_info.range_text(&node.span.range()); 80 | let line_count = lines_of_code(code); 81 | if line_count >= self.threshold { 82 | self.score += line_count as f64 / self.threshold as f64; 83 | } 84 | } 85 | 86 | fn visit_class_expr(&mut self, node: &ast::ClassExpr) { 87 | self.count += 1; 88 | let code = self.text_info.range_text(&node.class.span.range()); 89 | let line_count = lines_of_code(code); 90 | if line_count >= self.threshold { 91 | self.score += line_count as f64 / self.threshold as f64; 92 | } 93 | } 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | use url::Url; 100 | 101 | #[test] 102 | fn anon_inner_length_collector() { 103 | let source = r#" 104 | [].map((item) => { 105 | console.log(item); 106 | }); 107 | 108 | [].map((item) => console.log(item)); 109 | 110 | export class Foo { 111 | bar = new (class { 112 | constructor() { 113 | console.log("bar"); 114 | } 115 | })(); 116 | } 117 | 118 | [].map(() => { 119 | console.log("a"); 120 | console.log("a"); 121 | console.log("a"); 122 | console.log("a"); 123 | console.log("a"); 124 | console.log("a"); 125 | console.log("a"); 126 | console.log("a"); 127 | console.log("a"); 128 | console.log("a"); 129 | console.log("a"); 130 | console.log("a"); 131 | console.log("a"); 132 | console.log("a"); 133 | console.log("a"); 134 | console.log("a"); 135 | console.log("a"); 136 | console.log("a"); 137 | console.log("a"); 138 | console.log("a"); 139 | console.log("a"); 140 | console.log("a"); 141 | console.log("a"); 142 | console.log("a"); 143 | console.log("a"); 144 | console.log("a"); 145 | console.log("a"); 146 | console.log("a"); 147 | console.log("a"); 148 | console.log("a"); 149 | console.log("a"); 150 | console.log("a"); 151 | console.log("a"); 152 | }); 153 | "#; 154 | 155 | let parsed_source = deno_ast::parse_module(deno_ast::ParseParams { 156 | specifier: Url::parse("file://test/a.ts").unwrap(), 157 | text: source.into(), 158 | media_type: deno_ast::MediaType::TypeScript, 159 | capture_tokens: true, 160 | scope_analysis: false, 161 | maybe_syntax: None, 162 | }) 163 | .unwrap(); 164 | 165 | let mut collector = 166 | AnonInnerLengthCollector::new(35, parsed_source.text_info_lazy()); 167 | parsed_source.module().visit_with(&mut collector); 168 | assert_eq!(collector.count, 4); 169 | assert_eq!(collector.score, 1.0); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/stats/function_length.rs: -------------------------------------------------------------------------------- 1 | use deno_ast::swc::ast; 2 | use deno_ast::swc::visit::noop_visit_type; 3 | use deno_ast::swc::visit::Visit; 4 | use deno_ast::swc::visit::VisitWith; 5 | use deno_ast::SourceRangedForSpanned; 6 | use deno_ast::SourceTextInfo; 7 | use std::sync::Arc; 8 | 9 | use super::Stat; 10 | use super::StatLevel; 11 | use super::StatRecord; 12 | use crate::analysis::lines_of_code; 13 | use crate::context::Context; 14 | 15 | const CODE: &str = "function-length"; 16 | const SHORT_CODE: &str = "FL"; 17 | 18 | #[derive(Debug)] 19 | pub struct FunctionLength; 20 | 21 | impl Stat for FunctionLength { 22 | fn new() -> Arc { 23 | Arc::new(FunctionLength) 24 | } 25 | 26 | fn code(&self) -> &'static str { 27 | CODE 28 | } 29 | 30 | fn short_code(&self) -> &'static str { 31 | SHORT_CODE 32 | } 33 | 34 | fn stat<'view>( 35 | &self, 36 | context: &mut Context<'view>, 37 | maybe_threshold: Option, 38 | ) { 39 | let threshold = maybe_threshold.unwrap_or(30); 40 | let mut collector = FunctionLengthCollector::new( 41 | threshold, 42 | context.parsed_source.text_info_lazy(), 43 | ); 44 | context.parsed_source.module().visit_with(&mut collector); 45 | context.add_stat(StatRecord { 46 | metric: CODE.to_string(), 47 | metric_short: SHORT_CODE.to_string(), 48 | level: StatLevel::Function, 49 | threshold, 50 | count: collector.count, 51 | score: collector.score, 52 | }); 53 | } 54 | } 55 | 56 | struct FunctionLengthCollector<'view> { 57 | count: u32, 58 | score: f64, 59 | text_info: &'view SourceTextInfo, 60 | threshold: u32, 61 | } 62 | 63 | impl<'view> FunctionLengthCollector<'view> { 64 | pub fn new(threshold: u32, text_info: &'view SourceTextInfo) -> Self { 65 | Self { 66 | count: 0, 67 | score: 0.0, 68 | text_info, 69 | threshold, 70 | } 71 | } 72 | } 73 | 74 | impl<'view> Visit for FunctionLengthCollector<'view> { 75 | noop_visit_type!(); 76 | 77 | fn visit_function(&mut self, node: &ast::Function) { 78 | if let Some(body) = &node.body { 79 | self.count += 1; 80 | let code = self.text_info.range_text(&body.span.range()); 81 | let line_count = lines_of_code(code); 82 | if line_count >= self.threshold { 83 | self.score += line_count as f64 / self.threshold as f64; 84 | } 85 | } 86 | } 87 | 88 | fn visit_fn_decl(&mut self, node: &ast::FnDecl) { 89 | if let Some(body) = &node.function.body { 90 | self.count += 1; 91 | let code = self.text_info.range_text(&body.span.range()); 92 | let line_count = lines_of_code(code); 93 | if line_count >= self.threshold { 94 | self.score += line_count as f64 / self.threshold as f64; 95 | } 96 | } 97 | } 98 | 99 | fn visit_fn_expr(&mut self, node: &ast::FnExpr) { 100 | if let Some(body) = &node.function.body { 101 | self.count += 1; 102 | let code = self.text_info.range_text(&body.span.range()); 103 | let line_count = lines_of_code(code); 104 | if line_count >= self.threshold { 105 | self.score += line_count as f64 / self.threshold as f64; 106 | } 107 | } 108 | } 109 | 110 | fn visit_class_method(&mut self, node: &ast::ClassMethod) { 111 | if let Some(body) = &node.function.body { 112 | self.count += 1; 113 | let code = self.text_info.range_text(&body.span.range()); 114 | let line_count = lines_of_code(code); 115 | if line_count >= self.threshold { 116 | self.score += line_count as f64 / self.threshold as f64; 117 | } 118 | } 119 | } 120 | 121 | fn visit_private_method(&mut self, node: &ast::PrivateMethod) { 122 | if let Some(body) = &node.function.body { 123 | self.count += 1; 124 | let code = self.text_info.range_text(&body.span.range()); 125 | let line_count = lines_of_code(code); 126 | if line_count >= self.threshold { 127 | self.score += line_count as f64 / self.threshold as f64; 128 | } 129 | } 130 | } 131 | } 132 | 133 | #[cfg(test)] 134 | mod tests { 135 | use super::*; 136 | use url::Url; 137 | 138 | #[test] 139 | fn calculates_fn_length() { 140 | let source = r#"export class Foo { 141 | bar() {} 142 | } 143 | 144 | function bar() { 145 | return "bar"; 146 | } 147 | 148 | const baz = () => { 149 | console.log("baz"); 150 | }; 151 | 152 | const qat = function () { 153 | return "qat"; 154 | }; 155 | 156 | function qux() { 157 | console.log("qux"); 158 | console.log("qux"); 159 | console.log("qux"); 160 | console.log("qux"); 161 | console.log("qux"); 162 | console.log("qux"); 163 | console.log("qux"); 164 | console.log("qux"); 165 | console.log("qux"); 166 | console.log("qux"); 167 | console.log("qux"); 168 | console.log("qux"); 169 | console.log("qux"); 170 | console.log("qux"); 171 | console.log("qux"); 172 | console.log("qux"); 173 | console.log("qux"); 174 | console.log("qux"); 175 | console.log("qux"); 176 | console.log("qux"); 177 | console.log("qux"); 178 | console.log("qux"); 179 | console.log("qux"); 180 | console.log("qux"); 181 | console.log("qux"); 182 | console.log("qux"); 183 | console.log("qux"); 184 | console.log("qux"); 185 | } 186 | 187 | const quux = (t: boolean) => !t; 188 | "#; 189 | 190 | let parsed_source = deno_ast::parse_module(deno_ast::ParseParams { 191 | specifier: Url::parse("file://test/a.ts").unwrap(), 192 | text: source.into(), 193 | media_type: deno_ast::MediaType::TypeScript, 194 | capture_tokens: true, 195 | scope_analysis: false, 196 | maybe_syntax: None, 197 | }) 198 | .unwrap(); 199 | 200 | let mut collector = 201 | FunctionLengthCollector::new(30, parsed_source.text_info_lazy()); 202 | parsed_source.module().visit_with(&mut collector); 203 | assert_eq!(collector.count, 4); 204 | assert_eq!(collector.score, 1.0); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/project.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use anyhow::Result; 3 | use deno_ast::dep::analyze_module_dependencies; 4 | use deno_ast::dep::DependencyDescriptor; 5 | use deno_ast::parse_module; 6 | use deno_ast::MediaType; 7 | use deno_ast::ParseParams; 8 | use futures::future::Future; 9 | use futures::stream::FuturesUnordered; 10 | use futures::stream::StreamExt; 11 | use import_map::ImportMap; 12 | use indexmap::IndexMap; 13 | use indexmap::IndexSet; 14 | use std::collections::HashMap; 15 | use std::pin::Pin; 16 | use url::ParseError; 17 | use url::Url; 18 | use wasm_bindgen::prelude::*; 19 | 20 | use crate::context::Context; 21 | use crate::stats::get_all_stats; 22 | use crate::stats::StatRecord; 23 | 24 | type LoadFuture = Pin< 25 | Box< 26 | dyn Future, Option)>> 27 | + 'static, 28 | >, 29 | >; 30 | type LoadResponse = (Option, Option); 31 | 32 | pub struct Project { 33 | load: js_sys::Function, 34 | maybe_import_map: Option, 35 | maybe_resolve: Option, 36 | pending: FuturesUnordered, 37 | roots: Vec, 38 | specifiers: IndexSet, 39 | } 40 | 41 | impl Project { 42 | pub fn new( 43 | roots: Vec, 44 | load: js_sys::Function, 45 | maybe_resolve: Option, 46 | ) -> Self { 47 | Self { 48 | load, 49 | maybe_import_map: None, 50 | maybe_resolve, 51 | pending: FuturesUnordered::new(), 52 | roots, 53 | specifiers: IndexSet::new(), 54 | } 55 | } 56 | 57 | fn load(&mut self, specifier: &Url) -> Result<()> { 58 | let context = JsValue::null(); 59 | let arg1 = JsValue::from(specifier.to_string()); 60 | let result = self.load.call1(&context, &arg1); 61 | let specifier = specifier.clone(); 62 | let fut = async move { 63 | let response = match result { 64 | Ok(result) => { 65 | wasm_bindgen_futures::JsFuture::from(js_sys::Promise::resolve( 66 | &result, 67 | )) 68 | .await 69 | } 70 | Err(err) => Err(err), 71 | }; 72 | response 73 | .map(|value| { 74 | if let Ok((maybe_source, maybe_content_type)) = 75 | serde_wasm_bindgen::from_value::(value) 76 | { 77 | (specifier, maybe_source, maybe_content_type) 78 | } else { 79 | (specifier, None, None) 80 | } 81 | }) 82 | .map_err(|err| { 83 | let err = js_sys::Error::from(err); 84 | anyhow!("{}: {}", err.name(), err.message()) 85 | }) 86 | }; 87 | self.pending.push(Box::pin(fut)); 88 | Ok(()) 89 | } 90 | 91 | /// Attempt to resolve a URL from a string specifier using the referrer as a 92 | /// base if needed. 93 | fn resolve(&self, specifier: String, referrer: &Url) -> Result { 94 | // If a JS function was passed, use that to resolve... 95 | if let Some(resolve) = self.maybe_resolve.as_ref() { 96 | let context = JsValue::null(); 97 | let arg1 = JsValue::from(specifier); 98 | let arg2 = JsValue::from(referrer.to_string()); 99 | resolve 100 | .call2(&context, &arg1, &arg2) 101 | .map_err(|err| { 102 | anyhow!(err 103 | .as_string() 104 | .unwrap_or_else(|| "Unspecified error".to_string())) 105 | }) 106 | .and_then(|res| res.as_string().ok_or_else(|| anyhow!("bad"))) 107 | .and_then(|s| Url::parse(&s).map_err(|err| err.into())) 108 | } else if let Some(import_map) = self.maybe_import_map.as_ref() { 109 | // otherwise we will use an import map if present... 110 | import_map 111 | .resolve(&specifier, referrer) 112 | .map_err(|err| err.into()) 113 | } else { 114 | // otherwise we will just attempt to resolve the specifier using standard 115 | // logic 116 | Url::parse(&specifier).or_else(|err| match err { 117 | ParseError::RelativeUrlWithoutBase 118 | if !(specifier.starts_with('/') 119 | || specifier.starts_with("./") 120 | || specifier.starts_with("../")) => 121 | { 122 | Err(anyhow!("Specifier \"{}\" is not relative.", specifier)) 123 | } 124 | ParseError::RelativeUrlWithoutBase => { 125 | referrer.clone().join(&specifier).map_err(|err| err.into()) 126 | } 127 | _ => Err(err.into()), 128 | }) 129 | } 130 | } 131 | 132 | pub async fn stat(&mut self) -> Result>> { 133 | let mut results = IndexMap::>::new(); 134 | 135 | for specifier in self.roots.clone() { 136 | self.load(&specifier)?; 137 | } 138 | 139 | while let Some(Ok((specifier, maybe_source, maybe_content_type))) = 140 | self.pending.next().await 141 | { 142 | if let Some(source) = maybe_source { 143 | let maybe_headers = if let Some(content_type) = maybe_content_type { 144 | let mut headers = HashMap::new(); 145 | headers.insert("content-type".to_string(), content_type); 146 | Some(headers) 147 | } else { 148 | None 149 | }; 150 | let parsed_source = parse_module(ParseParams { 151 | specifier: specifier.clone(), 152 | text: source.into(), 153 | media_type: MediaType::from_specifier_and_headers( 154 | &specifier, 155 | maybe_headers.as_ref(), 156 | ), 157 | capture_tokens: true, 158 | scope_analysis: false, 159 | maybe_syntax: None, 160 | })?; 161 | 162 | let deps = analyze_module_dependencies( 163 | parsed_source.module(), 164 | parsed_source.comments(), 165 | ); 166 | 167 | for dep in deps { 168 | match dep { 169 | DependencyDescriptor::Static(descriptor) => { 170 | if let Ok(specifier) = 171 | self.resolve(descriptor.specifier.to_string(), &specifier) 172 | { 173 | if !self.specifiers.contains(&specifier) { 174 | self.load(&specifier)?; 175 | self.specifiers.insert(specifier); 176 | } 177 | } 178 | } 179 | _ => {} 180 | } 181 | } 182 | 183 | let stats = get_all_stats(); 184 | 185 | parsed_source.with_view(|program| { 186 | let mut context = Context::new(parsed_source.clone(), program); 187 | for stat in stats { 188 | stat.stat(&mut context, None); 189 | } 190 | results.insert(specifier.clone(), context.take()); 191 | }); 192 | } 193 | } 194 | 195 | Ok(results) 196 | } 197 | } 198 | 199 | #[cfg(test)] 200 | mod tests { 201 | use super::*; 202 | 203 | #[test] 204 | fn test_parse_module() { 205 | let file = parse_module(ParseParams { 206 | specifier: Url::from_file_path( 207 | "/home/kitsonk/github/tests/fixtures/a.ts", 208 | ) 209 | .unwrap(), 210 | text: r#" 211 | const a = require("fs"); 212 | "# 213 | .into(), 214 | media_type: MediaType::TypeScript, 215 | capture_tokens: true, 216 | scope_analysis: false, 217 | maybe_syntax: None, 218 | }) 219 | .unwrap(); 220 | let deps = analyze_module_dependencies(file.module(), file.comments()); 221 | println!("{:?}", deps); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/stats/cyclomatic_complexity.rs: -------------------------------------------------------------------------------- 1 | use deno_ast::swc::visit::VisitWith; 2 | use deno_ast::view; 3 | use deno_ast::view::NodeTrait; 4 | use std::sync::Arc; 5 | 6 | use super::binary_expression_complexity::BinaryExpressionComplexityCounter; 7 | use super::Stat; 8 | use super::StatLevel; 9 | use super::StatRecord; 10 | use crate::context::Context; 11 | use crate::walker::Traverse; 12 | use crate::walker::Walker; 13 | 14 | const CODE: &str = "cyclomatic-complexity"; 15 | const SHORT_CODE: &str = "CC"; 16 | 17 | #[derive(Debug)] 18 | pub struct CyclomaticComplexity; 19 | 20 | impl Stat for CyclomaticComplexity { 21 | fn new() -> Arc { 22 | Arc::new(Self) 23 | } 24 | 25 | fn code(&self) -> &'static str { 26 | CODE 27 | } 28 | 29 | fn short_code(&self) -> &'static str { 30 | SHORT_CODE 31 | } 32 | 33 | fn stat<'view>( 34 | &self, 35 | ctx: &mut Context<'view>, 36 | maybe_threshold: Option, 37 | ) { 38 | let threshold = maybe_threshold.unwrap_or(10); 39 | let mut walker = CyclomaticComplexityWalker::new(threshold); 40 | walker.traverse(ctx.program, ctx); 41 | ctx.add_stat(StatRecord { 42 | metric: CODE.to_string(), 43 | metric_short: SHORT_CODE.to_string(), 44 | level: StatLevel::Function, 45 | threshold, 46 | count: walker.count, 47 | score: walker.score, 48 | }) 49 | } 50 | } 51 | 52 | struct CyclomaticComplexityWalker { 53 | count: u32, 54 | score: f64, 55 | threshold: u32, 56 | } 57 | 58 | impl CyclomaticComplexityWalker { 59 | pub fn new(threshold: u32) -> Self { 60 | Self { 61 | count: 0, 62 | score: 0.0, 63 | threshold, 64 | } 65 | } 66 | } 67 | 68 | impl Walker for CyclomaticComplexityWalker { 69 | fn arrow_expr(&mut self, node: &view::ArrowExpr, ctx: &mut Context) { 70 | self.count += 1; 71 | let mut counter = CyclomaticComplexityCounter::new(); 72 | for child in node.children() { 73 | counter.traverse(child, ctx); 74 | } 75 | let complexity = counter.count(); 76 | if complexity >= self.threshold { 77 | self.score = complexity as f64 / self.threshold as f64; 78 | } 79 | } 80 | 81 | fn fn_decl(&mut self, node: &view::FnDecl, ctx: &mut Context) { 82 | self.count += 1; 83 | let mut counter = CyclomaticComplexityCounter::new(); 84 | for child in node.children() { 85 | counter.traverse(child, ctx); 86 | } 87 | let complexity = counter.count(); 88 | if complexity >= self.threshold { 89 | self.score = complexity as f64 / self.threshold as f64; 90 | } 91 | } 92 | 93 | fn fn_expr(&mut self, node: &view::FnExpr, ctx: &mut Context) { 94 | self.count += 1; 95 | let mut counter = CyclomaticComplexityCounter::new(); 96 | for child in node.children() { 97 | counter.traverse(child, ctx); 98 | } 99 | let complexity = counter.count(); 100 | if complexity >= self.threshold { 101 | self.score = complexity as f64 / self.threshold as f64; 102 | } 103 | } 104 | 105 | fn class_expr(&mut self, node: &view::ClassExpr, ctx: &mut Context) { 106 | self.count += 1; 107 | let mut counter = CyclomaticComplexityCounter::new(); 108 | for child in node.children() { 109 | counter.traverse(child, ctx); 110 | } 111 | let complexity = counter.count(); 112 | if complexity >= self.threshold { 113 | self.score = complexity as f64 / self.threshold as f64; 114 | } 115 | } 116 | 117 | fn class_method(&mut self, node: &view::ClassMethod, ctx: &mut Context) { 118 | self.count += 1; 119 | let mut counter = CyclomaticComplexityCounter::new(); 120 | for child in node.children() { 121 | counter.traverse(child, ctx); 122 | } 123 | let complexity = counter.count(); 124 | if complexity >= self.threshold { 125 | self.score = complexity as f64 / self.threshold as f64; 126 | } 127 | } 128 | 129 | fn private_method(&mut self, node: &view::PrivateMethod, ctx: &mut Context) { 130 | self.count += 1; 131 | let mut counter = CyclomaticComplexityCounter::new(); 132 | for child in node.children() { 133 | counter.traverse(child, ctx); 134 | } 135 | let complexity = counter.count(); 136 | if complexity >= self.threshold { 137 | self.score = complexity as f64 / self.threshold as f64; 138 | } 139 | } 140 | 141 | fn method_prop(&mut self, node: &view::MethodProp, ctx: &mut Context) { 142 | self.count += 1; 143 | let mut counter = CyclomaticComplexityCounter::new(); 144 | for child in node.children() { 145 | counter.traverse(child, ctx); 146 | } 147 | let complexity = counter.count(); 148 | if complexity >= self.threshold { 149 | self.score = complexity as f64 / self.threshold as f64; 150 | } 151 | } 152 | 153 | fn getter_prop(&mut self, node: &view::GetterProp, ctx: &mut Context) { 154 | self.count += 1; 155 | let mut counter = CyclomaticComplexityCounter::new(); 156 | for child in node.children() { 157 | counter.traverse(child, ctx); 158 | } 159 | let complexity = counter.count(); 160 | if complexity >= self.threshold { 161 | self.score = complexity as f64 / self.threshold as f64; 162 | } 163 | } 164 | 165 | fn setter_prop(&mut self, node: &view::SetterProp, ctx: &mut Context) { 166 | self.count += 1; 167 | let mut counter = CyclomaticComplexityCounter::new(); 168 | for child in node.children() { 169 | counter.traverse(child, ctx); 170 | } 171 | let complexity = counter.count(); 172 | if complexity >= self.threshold { 173 | self.score = complexity as f64 / self.threshold as f64; 174 | } 175 | } 176 | } 177 | 178 | struct CyclomaticComplexityCounter(u32); 179 | 180 | impl CyclomaticComplexityCounter { 181 | pub fn new() -> Self { 182 | Self(1) 183 | } 184 | 185 | pub fn count(&self) -> u32 { 186 | self.0 187 | } 188 | } 189 | 190 | impl Walker for CyclomaticComplexityCounter { 191 | fn bin_expr(&mut self, node: &view::BinExpr, _ctx: &mut Context) { 192 | let mut counter = BinaryExpressionComplexityCounter::new(); 193 | node.inner.visit_children_with(&mut counter); 194 | self.0 += counter.count(); 195 | } 196 | 197 | fn catch_clause(&mut self, _n: &view::CatchClause, _ctx: &mut Context) { 198 | self.0 += 1; 199 | } 200 | 201 | fn cond_expr(&mut self, _n: &view::CondExpr, _ctx: &mut Context) { 202 | self.0 += 1; 203 | } 204 | 205 | fn do_while_stmt(&mut self, node: &view::DoWhileStmt, _ctx: &mut Context) { 206 | if matches!( 207 | node.test, 208 | view::Expr::Bin(_) | view::Expr::Ident(_) | view::Expr::Unary(_) 209 | ) { 210 | self.0 += 1; 211 | } 212 | } 213 | 214 | fn for_in_stmt(&mut self, _n: &view::ForInStmt, _ctx: &mut Context) { 215 | self.0 += 1; 216 | } 217 | 218 | fn for_of_stmt(&mut self, _n: &view::ForOfStmt, _ctx: &mut Context) { 219 | self.0 += 1; 220 | } 221 | 222 | fn for_stmt(&mut self, node: &view::ForStmt, _ctx: &mut Context) { 223 | if matches!( 224 | node.test, 225 | Some(view::Expr::Bin(_)) 226 | | Some(view::Expr::Ident(_)) 227 | | Some(view::Expr::Unary(_)) 228 | ) { 229 | self.0 += 1; 230 | } 231 | } 232 | 233 | fn if_stmt(&mut self, node: &view::IfStmt, _ctx: &mut Context) { 234 | if node.alt.is_some() { 235 | self.0 += 2; 236 | } else { 237 | self.0 += 1; 238 | } 239 | } 240 | 241 | fn switch_case(&mut self, _n: &view::SwitchCase, _ctx: &mut Context) { 242 | self.0 += 1; 243 | } 244 | 245 | fn while_stmt(&mut self, node: &view::WhileStmt, _ctx: &mut Context) { 246 | if matches!( 247 | node.test, 248 | view::Expr::Bin(_) | view::Expr::Ident(_) | view::Expr::Unary(_) 249 | ) { 250 | self.0 += 1; 251 | } 252 | } 253 | 254 | // Boundaries of complexity 255 | 256 | fn arrow_expr(&mut self, _n: &view::ArrowExpr, ctx: &mut Context) { 257 | ctx.traversal.skip(); 258 | } 259 | 260 | fn class_expr(&mut self, _n: &view::ClassExpr, ctx: &mut Context) { 261 | ctx.traversal.skip(); 262 | } 263 | 264 | fn class_method(&mut self, _n: &view::ClassMethod, ctx: &mut Context) { 265 | ctx.traversal.skip(); 266 | } 267 | 268 | fn fn_decl(&mut self, _n: &view::FnDecl, ctx: &mut Context) { 269 | ctx.traversal.skip(); 270 | } 271 | 272 | fn fn_expr(&mut self, _n: &view::FnExpr, ctx: &mut Context) { 273 | ctx.traversal.skip(); 274 | } 275 | 276 | fn getter_prop(&mut self, _n: &view::GetterProp, ctx: &mut Context) { 277 | ctx.traversal.skip(); 278 | } 279 | 280 | fn method_prop(&mut self, _n: &view::MethodProp, ctx: &mut Context) { 281 | ctx.traversal.skip(); 282 | } 283 | 284 | fn private_method(&mut self, _n: &view::PrivateMethod, ctx: &mut Context) { 285 | ctx.traversal.skip(); 286 | } 287 | 288 | fn setter_prop(&mut self, _n: &view::SetterProp, ctx: &mut Context) { 289 | ctx.traversal.skip(); 290 | } 291 | } 292 | 293 | #[cfg(test)] 294 | mod tests { 295 | use super::*; 296 | use url::Url; 297 | 298 | #[test] 299 | fn calculates_complexity() { 300 | let source = r#"export class Foo { 301 | private _bar = "bar"; 302 | getBaz = (): boolean => { 303 | return ["baz"].some((item) => item === "baz"); 304 | }; 305 | getBar(): string { 306 | return this._bar; 307 | } 308 | get qat(): string { 309 | return "qat"; 310 | } 311 | } 312 | 313 | export function bar(): string { 314 | return "bar"; 315 | } 316 | 317 | export const baz = function (): string { 318 | return "baz"; 319 | }; 320 | 321 | export const qat = [{ foo: "foo" }].map((item) => item.foo); 322 | 323 | export function qux(a: string, b: string): string | number { 324 | if (a && typeof a === "string") { 325 | // 326 | } else if (b && typeof b === "string") { 327 | do { 328 | b = b.substr(1); 329 | } while (b.length); 330 | return b; 331 | } else if (a && b) { 332 | switch (a) { 333 | case "foo": 334 | return "foo"; 335 | case "bar": 336 | return "bar"; 337 | default: 338 | return "qat"; 339 | } 340 | } 341 | return 0; 342 | }"#; 343 | 344 | let parsed_source = deno_ast::parse_module(deno_ast::ParseParams { 345 | specifier: Url::parse("file://test/a.ts").unwrap(), 346 | text: source.into(), 347 | media_type: deno_ast::MediaType::TypeScript, 348 | capture_tokens: true, 349 | scope_analysis: false, 350 | maybe_syntax: None, 351 | }) 352 | .unwrap(); 353 | 354 | let mut walker = CyclomaticComplexityWalker::new(10); 355 | 356 | parsed_source.with_view(|program| { 357 | let mut ctx = Context::new(parsed_source.clone(), program); 358 | walker.traverse(ctx.program, &mut ctx); 359 | assert_eq!(walker.count, 8); 360 | assert_eq!(walker.score, 1.6); 361 | }); 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /src/walker.rs: -------------------------------------------------------------------------------- 1 | use deno_ast::view::*; 2 | 3 | use crate::context::Context; 4 | 5 | pub trait Walker { 6 | fn on_enter(&mut self, _node: Node, _ctx: &mut Context) {} 7 | fn on_exit(&mut self, _node: Node, _ctx: &mut Context) {} 8 | 9 | fn auto_accessor(&mut self, _n: &AutoAccessor, _ctx: &mut Context) {} 10 | fn array_lit(&mut self, _n: &ArrayLit, _ctx: &mut Context) {} 11 | fn array_pat(&mut self, _n: &ArrayPat, _ctx: &mut Context) {} 12 | fn arrow_expr(&mut self, _n: &ArrowExpr, _ctx: &mut Context) {} 13 | fn assign_expr(&mut self, _n: &AssignExpr, _ctx: &mut Context) {} 14 | fn assign_pat(&mut self, _n: &AssignPat, _ctx: &mut Context) {} 15 | fn assign_pat_prop(&mut self, _n: &AssignPatProp, _ctx: &mut Context) {} 16 | fn assign_prop(&mut self, _n: &AssignProp, _ctx: &mut Context) {} 17 | fn await_expr(&mut self, _n: &AwaitExpr, _ctx: &mut Context) {} 18 | fn big_int(&mut self, _n: &BigInt, _ctx: &mut Context) {} 19 | fn bin_expr(&mut self, _n: &BinExpr, _ctx: &mut Context) {} 20 | fn binding_ident(&mut self, _n: &BindingIdent, _ctx: &mut Context) {} 21 | fn block_stmt(&mut self, _n: &BlockStmt, _ctx: &mut Context) {} 22 | fn bool(&mut self, _n: &Bool, _ctx: &mut Context) {} 23 | fn break_stmt(&mut self, _n: &BreakStmt, _ctx: &mut Context) {} 24 | fn call_expr(&mut self, _n: &CallExpr, _ctx: &mut Context) {} 25 | fn catch_clause(&mut self, _n: &CatchClause, _ctx: &mut Context) {} 26 | fn class(&mut self, _n: &Class, _ctx: &mut Context) {} 27 | fn class_decl(&mut self, _n: &ClassDecl, _ctx: &mut Context) {} 28 | fn class_expr(&mut self, _n: &ClassExpr, _ctx: &mut Context) {} 29 | fn class_method(&mut self, _n: &ClassMethod, _ctx: &mut Context) {} 30 | fn class_prop(&mut self, _n: &ClassProp, _ctx: &mut Context) {} 31 | fn computed_prop_name(&mut self, _n: &ComputedPropName, _ctx: &mut Context) {} 32 | fn cond_expr(&mut self, _n: &CondExpr, _ctx: &mut Context) {} 33 | fn constructor(&mut self, _n: &Constructor, _ctx: &mut Context) {} 34 | fn continue_stmt(&mut self, _n: &ContinueStmt, _ctx: &mut Context) {} 35 | fn debugger_stmt(&mut self, _n: &DebuggerStmt, _ctx: &mut Context) {} 36 | fn decorator(&mut self, _n: &Decorator, _ctx: &mut Context) {} 37 | fn do_while_stmt(&mut self, _n: &DoWhileStmt, _ctx: &mut Context) {} 38 | fn empty_stmt(&mut self, _n: &EmptyStmt, _ctx: &mut Context) {} 39 | fn export_all(&mut self, _n: &ExportAll, _ctx: &mut Context) {} 40 | fn export_decl(&mut self, _n: &ExportDecl, _ctx: &mut Context) {} 41 | fn export_default_decl( 42 | &mut self, 43 | _n: &ExportDefaultDecl, 44 | _ctx: &mut Context, 45 | ) { 46 | } 47 | fn export_default_expr( 48 | &mut self, 49 | _n: &ExportDefaultExpr, 50 | _ctx: &mut Context, 51 | ) { 52 | } 53 | fn export_default_specifier( 54 | &mut self, 55 | _n: &ExportDefaultSpecifier, 56 | _ctx: &mut Context, 57 | ) { 58 | } 59 | fn export_named_specifier( 60 | &mut self, 61 | _n: &ExportNamedSpecifier, 62 | _ctx: &mut Context, 63 | ) { 64 | } 65 | fn export_namespace_specifier( 66 | &mut self, 67 | _n: &ExportNamespaceSpecifier, 68 | _ctx: &mut Context, 69 | ) { 70 | } 71 | fn expr_or_spread(&mut self, _n: &ExprOrSpread, _ctx: &mut Context) {} 72 | fn expr_stmt(&mut self, _n: &ExprStmt, _ctx: &mut Context) {} 73 | fn fn_decl(&mut self, _n: &FnDecl, _ctx: &mut Context) {} 74 | fn fn_expr(&mut self, _n: &FnExpr, _ctx: &mut Context) {} 75 | fn for_in_stmt(&mut self, _n: &ForInStmt, _ctx: &mut Context) {} 76 | fn for_of_stmt(&mut self, _n: &ForOfStmt, _ctx: &mut Context) {} 77 | fn for_stmt(&mut self, _n: &ForStmt, _ctx: &mut Context) {} 78 | fn function(&mut self, _n: &Function, _ctx: &mut Context) {} 79 | fn getter_prop(&mut self, _n: &GetterProp, _ctx: &mut Context) {} 80 | fn ident(&mut self, _n: &Ident, _ctx: &mut Context) {} 81 | fn ident_name(&mut self, _n: &IdentName, _ctx: &mut Context) {} 82 | fn if_stmt(&mut self, _n: &IfStmt, _ctx: &mut Context) {} 83 | fn import(&mut self, _n: &Import, _ctx: &mut Context) {} 84 | fn import_decl(&mut self, _n: &ImportDecl, _ctx: &mut Context) {} 85 | fn import_default_specifier( 86 | &mut self, 87 | _n: &ImportDefaultSpecifier, 88 | _ctx: &mut Context, 89 | ) { 90 | } 91 | fn import_named_specifier( 92 | &mut self, 93 | _n: &ImportNamedSpecifier, 94 | _ctx: &mut Context, 95 | ) { 96 | } 97 | fn import_star_as_specifier( 98 | &mut self, 99 | _n: &ImportStarAsSpecifier, 100 | _ctx: &mut Context, 101 | ) { 102 | } 103 | fn invalid(&mut self, _n: &Invalid, _ctx: &mut Context) {} 104 | fn jsx_attr(&mut self, _n: &JSXAttr, _ctx: &mut Context) {} 105 | fn jsx_closing_element( 106 | &mut self, 107 | _n: &JSXClosingElement, 108 | _ctx: &mut Context, 109 | ) { 110 | } 111 | fn jsx_closing_fragment( 112 | &mut self, 113 | _n: &JSXClosingFragment, 114 | _ctx: &mut Context, 115 | ) { 116 | } 117 | fn jsx_element(&mut self, _n: &JSXElement, _ctx: &mut Context) {} 118 | fn jsx_empty_expr(&mut self, _n: &JSXEmptyExpr, _ctx: &mut Context) {} 119 | fn jsx_expr_container(&mut self, _n: &JSXExprContainer, _ctx: &mut Context) {} 120 | fn jsx_fragment(&mut self, _n: &JSXFragment, _ctx: &mut Context) {} 121 | fn jsx_member_expr(&mut self, _n: &JSXMemberExpr, _ctx: &mut Context) {} 122 | fn jsx_namespaced_name( 123 | &mut self, 124 | _n: &JSXNamespacedName, 125 | _ctx: &mut Context, 126 | ) { 127 | } 128 | fn jsx_opening_element( 129 | &mut self, 130 | _n: &JSXOpeningElement, 131 | _ctx: &mut Context, 132 | ) { 133 | } 134 | fn jsx_opening_fragment( 135 | &mut self, 136 | _n: &JSXOpeningFragment, 137 | _ctx: &mut Context, 138 | ) { 139 | } 140 | fn jsx_spread_child(&mut self, _n: &JSXSpreadChild, _ctx: &mut Context) {} 141 | fn jsx_text(&mut self, _n: &JSXText, _ctx: &mut Context) {} 142 | fn key_value_pat_prop(&mut self, _n: &KeyValuePatProp, _ctx: &mut Context) {} 143 | fn key_value_prop(&mut self, _n: &KeyValueProp, _ctx: &mut Context) {} 144 | fn labeled_stmt(&mut self, _n: &LabeledStmt, _ctx: &mut Context) {} 145 | fn member_expr(&mut self, _n: &MemberExpr, _ctx: &mut Context) {} 146 | fn meta_prop_expr(&mut self, _n: &MetaPropExpr, _ctx: &mut Context) {} 147 | fn method_prop(&mut self, _n: &MethodProp, _ctx: &mut Context) {} 148 | fn module(&mut self, _n: &Module, _ctx: &mut Context) {} 149 | fn named_export(&mut self, _n: &NamedExport, _ctx: &mut Context) {} 150 | fn new_expr(&mut self, _n: &NewExpr, _ctx: &mut Context) {} 151 | fn null(&mut self, _n: &Null, _ctx: &mut Context) {} 152 | fn number(&mut self, _n: &Number, _ctx: &mut Context) {} 153 | fn object_lit(&mut self, _n: &ObjectLit, _ctx: &mut Context) {} 154 | fn object_pat(&mut self, _n: &ObjectPat, _ctx: &mut Context) {} 155 | fn opt_chain_expr(&mut self, _n: &OptChainExpr, _ctx: &mut Context) {} 156 | fn opt_call(&mut self, _n: &OptCall, _ctx: &mut Context) {} 157 | fn param(&mut self, _n: &Param, _ctx: &mut Context) {} 158 | fn paren_expr(&mut self, _n: &ParenExpr, _ctx: &mut Context) {} 159 | fn private_method(&mut self, _n: &PrivateMethod, _ctx: &mut Context) {} 160 | fn private_name(&mut self, _n: &PrivateName, _ctx: &mut Context) {} 161 | fn private_prop(&mut self, _n: &PrivateProp, _ctx: &mut Context) {} 162 | fn regex(&mut self, _n: &Regex, _ctx: &mut Context) {} 163 | fn rest_pat(&mut self, _n: &RestPat, _ctx: &mut Context) {} 164 | fn return_stmt(&mut self, _n: &ReturnStmt, _ctx: &mut Context) {} 165 | fn script(&mut self, _n: &Script, _ctx: &mut Context) {} 166 | fn seq_expr(&mut self, _n: &SeqExpr, _ctx: &mut Context) {} 167 | fn setter_prop(&mut self, _n: &SetterProp, _ctx: &mut Context) {} 168 | fn spread_element(&mut self, _n: &SpreadElement, _ctx: &mut Context) {} 169 | fn static_block(&mut self, _n: &StaticBlock, _ctx: &mut Context) {} 170 | fn str(&mut self, _n: &Str, _ctx: &mut Context) {} 171 | fn super_(&mut self, _n: &Super, _ctx: &mut Context) {} 172 | fn super_prop_expr(&mut self, _n: &SuperPropExpr, _ctx: &mut Context) {} 173 | fn switch_case(&mut self, _n: &SwitchCase, _ctx: &mut Context) {} 174 | fn switch_stmt(&mut self, _n: &SwitchStmt, _ctx: &mut Context) {} 175 | fn tagged_tpl(&mut self, _n: &TaggedTpl, _ctx: &mut Context) {} 176 | fn this_expr(&mut self, _n: &ThisExpr, _ctx: &mut Context) {} 177 | fn throw_stmt(&mut self, _n: &ThrowStmt, _ctx: &mut Context) {} 178 | fn tpl(&mut self, _n: &Tpl, _ctx: &mut Context) {} 179 | fn tpl_element(&mut self, _n: &TplElement, _ctx: &mut Context) {} 180 | fn try_stmt(&mut self, _n: &TryStmt, _ctx: &mut Context) {} 181 | fn ts_array_type(&mut self, _n: &TsArrayType, _ctx: &mut Context) {} 182 | fn ts_as_expr(&mut self, _n: &TsAsExpr, _ctx: &mut Context) {} 183 | fn ts_satisfies_expr(&mut self, _n: &TsSatisfiesExpr, _ctx: &mut Context) {} 184 | fn ts_call_signature_decl( 185 | &mut self, 186 | _n: &TsCallSignatureDecl, 187 | _ctx: &mut Context, 188 | ) { 189 | } 190 | fn ts_conditional_type( 191 | &mut self, 192 | _n: &TsConditionalType, 193 | _ctx: &mut Context, 194 | ) { 195 | } 196 | fn ts_const_assertion(&mut self, _n: &TsConstAssertion, _ctx: &mut Context) {} 197 | fn ts_construct_signature_decl( 198 | &mut self, 199 | _n: &TsConstructSignatureDecl, 200 | _ctx: &mut Context, 201 | ) { 202 | } 203 | fn ts_constructor_type( 204 | &mut self, 205 | _n: &TsConstructorType, 206 | _ctx: &mut Context, 207 | ) { 208 | } 209 | fn ts_enum_decl(&mut self, _n: &TsEnumDecl, _ctx: &mut Context) {} 210 | fn ts_enum_member(&mut self, _n: &TsEnumMember, _ctx: &mut Context) {} 211 | fn ts_export_assignment( 212 | &mut self, 213 | _n: &TsExportAssignment, 214 | _ctx: &mut Context, 215 | ) { 216 | } 217 | fn ts_expr_with_type_args( 218 | &mut self, 219 | _n: &TsExprWithTypeArgs, 220 | _ctx: &mut Context, 221 | ) { 222 | } 223 | fn ts_external_module_ref( 224 | &mut self, 225 | _n: &TsExternalModuleRef, 226 | _ctx: &mut Context, 227 | ) { 228 | } 229 | fn ts_fn_type(&mut self, _n: &TsFnType, _ctx: &mut Context) {} 230 | fn ts_getter_signature( 231 | &mut self, 232 | _n: &TsGetterSignature, 233 | _ctx: &mut Context, 234 | ) { 235 | } 236 | fn ts_import_equal_decl( 237 | &mut self, 238 | _n: &TsImportEqualsDecl, 239 | _ctx: &mut Context, 240 | ) { 241 | } 242 | fn ts_import_type(&mut self, _n: &TsImportType, _ctx: &mut Context) {} 243 | fn ts_index_signature(&mut self, _n: &TsIndexSignature, _ctx: &mut Context) {} 244 | fn ts_indexed_access_type( 245 | &mut self, 246 | _n: &TsIndexedAccessType, 247 | _ctx: &mut Context, 248 | ) { 249 | } 250 | fn ts_infer_type(&mut self, _n: &TsInferType, _ctx: &mut Context) {} 251 | fn ts_instantiation(&mut self, _n: &TsInstantiation, _ctx: &mut Context) {} 252 | fn ts_interface_body(&mut self, _n: &TsInterfaceBody, _ctx: &mut Context) {} 253 | fn ts_interface_decl(&mut self, _n: &TsInterfaceDecl, _ctx: &mut Context) {} 254 | fn ts_intersection_type( 255 | &mut self, 256 | _n: &TsIntersectionType, 257 | _ctx: &mut Context, 258 | ) { 259 | } 260 | fn ts_keyword_type(&mut self, _n: &TsKeywordType, _ctx: &mut Context) {} 261 | fn ts_lit_type(&mut self, _n: &TsLitType, _ctx: &mut Context) {} 262 | fn ts_mapped_type(&mut self, _n: &TsMappedType, _ctx: &mut Context) {} 263 | fn ts_method_signature( 264 | &mut self, 265 | _n: &TsMethodSignature, 266 | _ctx: &mut Context, 267 | ) { 268 | } 269 | fn ts_module_block(&mut self, _n: &TsModuleBlock, _ctx: &mut Context) {} 270 | fn ts_module_decl(&mut self, _n: &TsModuleDecl, _ctx: &mut Context) {} 271 | fn ts_namespace_decl(&mut self, _n: &TsNamespaceDecl, _ctx: &mut Context) {} 272 | fn ts_namespace_export_decl( 273 | &mut self, 274 | _n: &TsNamespaceExportDecl, 275 | _ctx: &mut Context, 276 | ) { 277 | } 278 | fn ts_non_null_expr(&mut self, _n: &TsNonNullExpr, _ctx: &mut Context) {} 279 | fn ts_optional_type(&mut self, _n: &TsOptionalType, _ctx: &mut Context) {} 280 | fn ts_param_prop(&mut self, _n: &TsParamProp, _ctx: &mut Context) {} 281 | fn ts_parenthesized_type( 282 | &mut self, 283 | _n: &TsParenthesizedType, 284 | _ctx: &mut Context, 285 | ) { 286 | } 287 | fn ts_property_signature( 288 | &mut self, 289 | _n: &TsPropertySignature, 290 | _ctx: &mut Context, 291 | ) { 292 | } 293 | fn ts_qualified_name(&mut self, _n: &TsQualifiedName, _ctx: &mut Context) {} 294 | fn ts_rest_type(&mut self, _n: &TsRestType, _ctx: &mut Context) {} 295 | fn ts_setter_signature( 296 | &mut self, 297 | _n: &TsSetterSignature, 298 | _ctx: &mut Context, 299 | ) { 300 | } 301 | fn ts_this_type(&mut self, _n: &TsThisType, _ctx: &mut Context) {} 302 | fn ts_tpl_lit_type(&mut self, _n: &TsTplLitType, _ctx: &mut Context) {} 303 | fn ts_tuple_element(&mut self, _n: &TsTupleElement, _ctx: &mut Context) {} 304 | fn ts_tuple_type(&mut self, _n: &TsTupleType, _ctx: &mut Context) {} 305 | fn ts_type_alias_decl(&mut self, _n: &TsTypeAliasDecl, _ctx: &mut Context) {} 306 | fn ts_type_ann(&mut self, _n: &TsTypeAnn, _ctx: &mut Context) {} 307 | fn ts_type_assertion(&mut self, _n: &TsTypeAssertion, _ctx: &mut Context) {} 308 | fn ts_type_lit(&mut self, _n: &TsTypeLit, _ctx: &mut Context) {} 309 | fn ts_type_operator(&mut self, _n: &TsTypeOperator, _ctx: &mut Context) {} 310 | fn ts_type_param(&mut self, _n: &TsTypeParam, _ctx: &mut Context) {} 311 | fn ts_type_param_decl(&mut self, _n: &TsTypeParamDecl, _ctx: &mut Context) {} 312 | fn ts_type_param_instantiation( 313 | &mut self, 314 | _n: &TsTypeParamInstantiation, 315 | _ctx: &mut Context, 316 | ) { 317 | } 318 | fn ts_type_predicate(&mut self, _n: &TsTypePredicate, _ctx: &mut Context) {} 319 | fn ts_type_query(&mut self, _n: &TsTypeQuery, _ctx: &mut Context) {} 320 | fn ts_type_ref(&mut self, _n: &TsTypeRef, _ctx: &mut Context) {} 321 | fn ts_union_type(&mut self, _n: &TsUnionType, _ctx: &mut Context) {} 322 | fn unary_expr(&mut self, _n: &UnaryExpr, _ctx: &mut Context) {} 323 | fn update_expr(&mut self, _n: &UpdateExpr, _ctx: &mut Context) {} 324 | fn using_decl(&mut self, _n: &UsingDecl, _ctx: &mut Context) {} 325 | fn var_decl(&mut self, _n: &VarDecl, _ctx: &mut Context) {} 326 | fn var_declarator(&mut self, _n: &VarDeclarator, _ctx: &mut Context) {} 327 | fn while_stmt(&mut self, _n: &WhileStmt, _ctx: &mut Context) {} 328 | fn with_stmt(&mut self, _n: &WithStmt, _ctx: &mut Context) {} 329 | fn yield_expr(&mut self, _n: &YieldExpr, _ctx: &mut Context) {} 330 | } 331 | 332 | pub trait Traverse: Walker { 333 | fn traverse<'view, N>(&mut self, node: N, ctx: &mut Context) 334 | where 335 | N: NodeTrait<'view>, 336 | { 337 | let node = node.as_node(); 338 | 339 | ctx.traversal.reset(); 340 | 341 | self.on_enter(node, ctx); 342 | 343 | use deno_ast::view::Node::*; 344 | match node { 345 | AutoAccessor(n) => self.auto_accessor(n, ctx), 346 | ArrayLit(n) => self.array_lit(n, ctx), 347 | ArrayPat(n) => self.array_pat(n, ctx), 348 | ArrowExpr(n) => self.arrow_expr(n, ctx), 349 | AssignExpr(n) => self.assign_expr(n, ctx), 350 | AssignPat(n) => self.assign_pat(n, ctx), 351 | AssignPatProp(n) => self.assign_pat_prop(n, ctx), 352 | AssignProp(n) => self.assign_prop(n, ctx), 353 | AwaitExpr(n) => self.await_expr(n, ctx), 354 | BigInt(n) => self.big_int(n, ctx), 355 | BinExpr(n) => self.bin_expr(n, ctx), 356 | BindingIdent(n) => self.binding_ident(n, ctx), 357 | BlockStmt(n) => self.block_stmt(n, ctx), 358 | Bool(n) => self.bool(n, ctx), 359 | BreakStmt(n) => self.break_stmt(n, ctx), 360 | CallExpr(n) => self.call_expr(n, ctx), 361 | CatchClause(n) => self.catch_clause(n, ctx), 362 | Class(n) => self.class(n, ctx), 363 | ClassDecl(n) => self.class_decl(n, ctx), 364 | ClassExpr(n) => self.class_expr(n, ctx), 365 | ClassMethod(n) => self.class_method(n, ctx), 366 | ClassProp(n) => self.class_prop(n, ctx), 367 | ComputedPropName(n) => self.computed_prop_name(n, ctx), 368 | CondExpr(n) => self.cond_expr(n, ctx), 369 | Constructor(n) => self.constructor(n, ctx), 370 | ContinueStmt(n) => self.continue_stmt(n, ctx), 371 | DebuggerStmt(n) => self.debugger_stmt(n, ctx), 372 | Decorator(n) => self.decorator(n, ctx), 373 | DoWhileStmt(n) => self.do_while_stmt(n, ctx), 374 | EmptyStmt(n) => self.empty_stmt(n, ctx), 375 | ExportAll(n) => self.export_all(n, ctx), 376 | ExportDecl(n) => self.export_decl(n, ctx), 377 | ExportDefaultDecl(n) => self.export_default_decl(n, ctx), 378 | ExportDefaultExpr(n) => self.export_default_expr(n, ctx), 379 | ExportDefaultSpecifier(n) => self.export_default_specifier(n, ctx), 380 | ExportNamedSpecifier(n) => self.export_named_specifier(n, ctx), 381 | ExportNamespaceSpecifier(n) => self.export_namespace_specifier(n, ctx), 382 | ExprOrSpread(n) => self.expr_or_spread(n, ctx), 383 | ExprStmt(n) => self.expr_stmt(n, ctx), 384 | FnDecl(n) => self.fn_decl(n, ctx), 385 | FnExpr(n) => self.fn_expr(n, ctx), 386 | ForInStmt(n) => self.for_in_stmt(n, ctx), 387 | ForOfStmt(n) => self.for_of_stmt(n, ctx), 388 | ForStmt(n) => self.for_stmt(n, ctx), 389 | Function(n) => self.function(n, ctx), 390 | GetterProp(n) => self.getter_prop(n, ctx), 391 | Ident(n) => self.ident(n, ctx), 392 | IdentName(n) => self.ident_name(n, ctx), 393 | IfStmt(n) => self.if_stmt(n, ctx), 394 | Import(n) => self.import(n, ctx), 395 | ImportDecl(n) => self.import_decl(n, ctx), 396 | ImportDefaultSpecifier(n) => self.import_default_specifier(n, ctx), 397 | ImportNamedSpecifier(n) => self.import_named_specifier(n, ctx), 398 | ImportStarAsSpecifier(n) => self.import_star_as_specifier(n, ctx), 399 | Invalid(n) => self.invalid(n, ctx), 400 | JSXAttr(n) => self.jsx_attr(n, ctx), 401 | JSXClosingElement(n) => self.jsx_closing_element(n, ctx), 402 | JSXClosingFragment(n) => self.jsx_closing_fragment(n, ctx), 403 | JSXElement(n) => self.jsx_element(n, ctx), 404 | JSXEmptyExpr(n) => self.jsx_empty_expr(n, ctx), 405 | JSXExprContainer(n) => self.jsx_expr_container(n, ctx), 406 | JSXFragment(n) => self.jsx_fragment(n, ctx), 407 | JSXMemberExpr(n) => self.jsx_member_expr(n, ctx), 408 | JSXNamespacedName(n) => self.jsx_namespaced_name(n, ctx), 409 | JSXOpeningElement(n) => self.jsx_opening_element(n, ctx), 410 | JSXOpeningFragment(n) => self.jsx_opening_fragment(n, ctx), 411 | JSXSpreadChild(n) => self.jsx_spread_child(n, ctx), 412 | JSXText(n) => self.jsx_text(n, ctx), 413 | KeyValuePatProp(n) => self.key_value_pat_prop(n, ctx), 414 | KeyValueProp(n) => self.key_value_prop(n, ctx), 415 | LabeledStmt(n) => self.labeled_stmt(n, ctx), 416 | MemberExpr(n) => self.member_expr(n, ctx), 417 | MetaPropExpr(n) => self.meta_prop_expr(n, ctx), 418 | MethodProp(n) => self.method_prop(n, ctx), 419 | Module(n) => self.module(n, ctx), 420 | NamedExport(n) => self.named_export(n, ctx), 421 | NewExpr(n) => self.new_expr(n, ctx), 422 | Null(n) => self.null(n, ctx), 423 | Number(n) => self.number(n, ctx), 424 | ObjectLit(n) => self.object_lit(n, ctx), 425 | ObjectPat(n) => self.object_pat(n, ctx), 426 | OptChainExpr(n) => self.opt_chain_expr(n, ctx), 427 | OptCall(n) => self.opt_call(n, ctx), 428 | Param(n) => self.param(n, ctx), 429 | ParenExpr(n) => self.paren_expr(n, ctx), 430 | PrivateMethod(n) => self.private_method(n, ctx), 431 | PrivateName(n) => self.private_name(n, ctx), 432 | PrivateProp(n) => self.private_prop(n, ctx), 433 | Regex(n) => self.regex(n, ctx), 434 | RestPat(n) => self.rest_pat(n, ctx), 435 | ReturnStmt(n) => self.return_stmt(n, ctx), 436 | Script(n) => self.script(n, ctx), 437 | SeqExpr(n) => self.seq_expr(n, ctx), 438 | SetterProp(n) => self.setter_prop(n, ctx), 439 | SpreadElement(n) => self.spread_element(n, ctx), 440 | StaticBlock(n) => self.static_block(n, ctx), 441 | Str(n) => self.str(n, ctx), 442 | Super(n) => self.super_(n, ctx), 443 | SuperPropExpr(n) => self.super_prop_expr(n, ctx), 444 | SwitchCase(n) => self.switch_case(n, ctx), 445 | SwitchStmt(n) => self.switch_stmt(n, ctx), 446 | TaggedTpl(n) => self.tagged_tpl(n, ctx), 447 | ThisExpr(n) => self.this_expr(n, ctx), 448 | ThrowStmt(n) => self.throw_stmt(n, ctx), 449 | Tpl(n) => self.tpl(n, ctx), 450 | TplElement(n) => self.tpl_element(n, ctx), 451 | TryStmt(n) => self.try_stmt(n, ctx), 452 | TsArrayType(n) => self.ts_array_type(n, ctx), 453 | TsAsExpr(n) => self.ts_as_expr(n, ctx), 454 | TsCallSignatureDecl(n) => self.ts_call_signature_decl(n, ctx), 455 | TsConditionalType(n) => self.ts_conditional_type(n, ctx), 456 | TsConstAssertion(n) => self.ts_const_assertion(n, ctx), 457 | TsConstructSignatureDecl(n) => self.ts_construct_signature_decl(n, ctx), 458 | TsConstructorType(n) => self.ts_constructor_type(n, ctx), 459 | TsEnumDecl(n) => self.ts_enum_decl(n, ctx), 460 | TsEnumMember(n) => self.ts_enum_member(n, ctx), 461 | TsExportAssignment(n) => self.ts_export_assignment(n, ctx), 462 | TsExprWithTypeArgs(n) => self.ts_expr_with_type_args(n, ctx), 463 | TsExternalModuleRef(n) => self.ts_external_module_ref(n, ctx), 464 | TsFnType(n) => self.ts_fn_type(n, ctx), 465 | TsGetterSignature(n) => self.ts_getter_signature(n, ctx), 466 | TsImportEqualsDecl(n) => self.ts_import_equal_decl(n, ctx), 467 | TsImportType(n) => self.ts_import_type(n, ctx), 468 | TsIndexSignature(n) => self.ts_index_signature(n, ctx), 469 | TsIndexedAccessType(n) => self.ts_indexed_access_type(n, ctx), 470 | TsInferType(n) => self.ts_infer_type(n, ctx), 471 | TsInstantiation(n) => self.ts_instantiation(n, ctx), 472 | TsInterfaceBody(n) => self.ts_interface_body(n, ctx), 473 | TsInterfaceDecl(n) => self.ts_interface_decl(n, ctx), 474 | TsIntersectionType(n) => self.ts_intersection_type(n, ctx), 475 | TsKeywordType(n) => self.ts_keyword_type(n, ctx), 476 | TsLitType(n) => self.ts_lit_type(n, ctx), 477 | TsMappedType(n) => self.ts_mapped_type(n, ctx), 478 | TsMethodSignature(n) => self.ts_method_signature(n, ctx), 479 | TsModuleBlock(n) => self.ts_module_block(n, ctx), 480 | TsModuleDecl(n) => self.ts_module_decl(n, ctx), 481 | TsNamespaceDecl(n) => self.ts_namespace_decl(n, ctx), 482 | TsNamespaceExportDecl(n) => self.ts_namespace_export_decl(n, ctx), 483 | TsNonNullExpr(n) => self.ts_non_null_expr(n, ctx), 484 | TsOptionalType(n) => self.ts_optional_type(n, ctx), 485 | TsParamProp(n) => self.ts_param_prop(n, ctx), 486 | TsParenthesizedType(n) => self.ts_parenthesized_type(n, ctx), 487 | TsPropertySignature(n) => self.ts_property_signature(n, ctx), 488 | TsQualifiedName(n) => self.ts_qualified_name(n, ctx), 489 | TsRestType(n) => self.ts_rest_type(n, ctx), 490 | TsSatisfiesExpr(n) => self.ts_satisfies_expr(n, ctx), 491 | TsSetterSignature(n) => self.ts_setter_signature(n, ctx), 492 | TsThisType(n) => self.ts_this_type(n, ctx), 493 | TsTplLitType(n) => self.ts_tpl_lit_type(n, ctx), 494 | TsTupleElement(n) => self.ts_tuple_element(n, ctx), 495 | TsTupleType(n) => self.ts_tuple_type(n, ctx), 496 | TsTypeAliasDecl(n) => self.ts_type_alias_decl(n, ctx), 497 | TsTypeAnn(n) => self.ts_type_ann(n, ctx), 498 | TsTypeAssertion(n) => self.ts_type_assertion(n, ctx), 499 | TsTypeLit(n) => self.ts_type_lit(n, ctx), 500 | TsTypeOperator(n) => self.ts_type_operator(n, ctx), 501 | TsTypeParam(n) => self.ts_type_param(n, ctx), 502 | TsTypeParamDecl(n) => self.ts_type_param_decl(n, ctx), 503 | TsTypeParamInstantiation(n) => self.ts_type_param_instantiation(n, ctx), 504 | TsTypePredicate(n) => self.ts_type_predicate(n, ctx), 505 | TsTypeQuery(n) => self.ts_type_query(n, ctx), 506 | TsTypeRef(n) => self.ts_type_ref(n, ctx), 507 | TsUnionType(n) => self.ts_union_type(n, ctx), 508 | UnaryExpr(n) => self.unary_expr(n, ctx), 509 | UpdateExpr(n) => self.update_expr(n, ctx), 510 | UsingDecl(n) => self.using_decl(n, ctx), 511 | VarDecl(n) => self.var_decl(n, ctx), 512 | VarDeclarator(n) => self.var_declarator(n, ctx), 513 | WhileStmt(n) => self.while_stmt(n, ctx), 514 | WithStmt(n) => self.with_stmt(n, ctx), 515 | YieldExpr(n) => self.yield_expr(n, ctx), 516 | }; 517 | 518 | if !ctx.traversal.should_skip() { 519 | for child in node.children() { 520 | self.traverse(child, ctx); 521 | } 522 | } 523 | 524 | self.on_exit(node, ctx); 525 | } 526 | } 527 | 528 | impl Traverse for W {} 529 | -------------------------------------------------------------------------------- /lib/nocuous.generated.js: -------------------------------------------------------------------------------- 1 | // @generated file from wasmbuild -- do not edit 2 | // @ts-nocheck: generated 3 | // deno-lint-ignore-file 4 | // deno-fmt-ignore-file 5 | /// 6 | 7 | // source-hash: 761ce91bffc95fee1df1c671444b2ec475ac40b1 8 | let wasm; 9 | 10 | const heap = new Array(128).fill(undefined); 11 | 12 | heap.push(undefined, null, true, false); 13 | 14 | function getObject(idx) { 15 | return heap[idx]; 16 | } 17 | 18 | let heap_next = heap.length; 19 | 20 | function dropObject(idx) { 21 | if (idx < 132) return; 22 | heap[idx] = heap_next; 23 | heap_next = idx; 24 | } 25 | 26 | function takeObject(idx) { 27 | const ret = getObject(idx); 28 | dropObject(idx); 29 | return ret; 30 | } 31 | 32 | const cachedTextDecoder = typeof TextDecoder !== "undefined" 33 | ? new TextDecoder("utf-8", { ignoreBOM: true, fatal: true }) 34 | : { 35 | decode: () => { 36 | throw Error("TextDecoder not available"); 37 | }, 38 | }; 39 | 40 | if (typeof TextDecoder !== "undefined") cachedTextDecoder.decode(); 41 | 42 | let cachedUint8Memory0 = null; 43 | 44 | function getUint8Memory0() { 45 | if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { 46 | cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); 47 | } 48 | return cachedUint8Memory0; 49 | } 50 | 51 | function getStringFromWasm0(ptr, len) { 52 | ptr = ptr >>> 0; 53 | return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); 54 | } 55 | 56 | function addHeapObject(obj) { 57 | if (heap_next === heap.length) heap.push(heap.length + 1); 58 | const idx = heap_next; 59 | heap_next = heap[idx]; 60 | 61 | heap[idx] = obj; 62 | return idx; 63 | } 64 | 65 | let WASM_VECTOR_LEN = 0; 66 | 67 | const cachedTextEncoder = typeof TextEncoder !== "undefined" 68 | ? new TextEncoder("utf-8") 69 | : { 70 | encode: () => { 71 | throw Error("TextEncoder not available"); 72 | }, 73 | }; 74 | 75 | const encodeString = function (arg, view) { 76 | return cachedTextEncoder.encodeInto(arg, view); 77 | }; 78 | 79 | function passStringToWasm0(arg, malloc, realloc) { 80 | if (realloc === undefined) { 81 | const buf = cachedTextEncoder.encode(arg); 82 | const ptr = malloc(buf.length, 1) >>> 0; 83 | getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); 84 | WASM_VECTOR_LEN = buf.length; 85 | return ptr; 86 | } 87 | 88 | let len = arg.length; 89 | let ptr = malloc(len, 1) >>> 0; 90 | 91 | const mem = getUint8Memory0(); 92 | 93 | let offset = 0; 94 | 95 | for (; offset < len; offset++) { 96 | const code = arg.charCodeAt(offset); 97 | if (code > 0x7F) break; 98 | mem[ptr + offset] = code; 99 | } 100 | 101 | if (offset !== len) { 102 | if (offset !== 0) { 103 | arg = arg.slice(offset); 104 | } 105 | ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; 106 | const view = getUint8Memory0().subarray(ptr + offset, ptr + len); 107 | const ret = encodeString(arg, view); 108 | 109 | offset += ret.written; 110 | ptr = realloc(ptr, len, offset, 1) >>> 0; 111 | } 112 | 113 | WASM_VECTOR_LEN = offset; 114 | return ptr; 115 | } 116 | 117 | function isLikeNone(x) { 118 | return x === undefined || x === null; 119 | } 120 | 121 | let cachedInt32Memory0 = null; 122 | 123 | function getInt32Memory0() { 124 | if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { 125 | cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); 126 | } 127 | return cachedInt32Memory0; 128 | } 129 | 130 | let cachedFloat64Memory0 = null; 131 | 132 | function getFloat64Memory0() { 133 | if (cachedFloat64Memory0 === null || cachedFloat64Memory0.byteLength === 0) { 134 | cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer); 135 | } 136 | return cachedFloat64Memory0; 137 | } 138 | 139 | function debugString(val) { 140 | // primitive types 141 | const type = typeof val; 142 | if (type == "number" || type == "boolean" || val == null) { 143 | return `${val}`; 144 | } 145 | if (type == "string") { 146 | return `"${val}"`; 147 | } 148 | if (type == "symbol") { 149 | const description = val.description; 150 | if (description == null) { 151 | return "Symbol"; 152 | } else { 153 | return `Symbol(${description})`; 154 | } 155 | } 156 | if (type == "function") { 157 | const name = val.name; 158 | if (typeof name == "string" && name.length > 0) { 159 | return `Function(${name})`; 160 | } else { 161 | return "Function"; 162 | } 163 | } 164 | // objects 165 | if (Array.isArray(val)) { 166 | const length = val.length; 167 | let debug = "["; 168 | if (length > 0) { 169 | debug += debugString(val[0]); 170 | } 171 | for (let i = 1; i < length; i++) { 172 | debug += ", " + debugString(val[i]); 173 | } 174 | debug += "]"; 175 | return debug; 176 | } 177 | // Test for built-in 178 | const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); 179 | let className; 180 | if (builtInMatches.length > 1) { 181 | className = builtInMatches[1]; 182 | } else { 183 | // Failed to match the standard '[object ClassName]' 184 | return toString.call(val); 185 | } 186 | if (className == "Object") { 187 | // we're a user defined class or Object 188 | // JSON.stringify avoids problems with cycles, and is generally much 189 | // easier than looping through ownProperties of `val`. 190 | try { 191 | return "Object(" + JSON.stringify(val) + ")"; 192 | } catch (_) { 193 | return "Object"; 194 | } 195 | } 196 | // errors 197 | if (val instanceof Error) { 198 | return `${val.name}: ${val.message}\n${val.stack}`; 199 | } 200 | // TODO we could test for more things here, like `Set`s and `Map`s. 201 | return className; 202 | } 203 | 204 | const CLOSURE_DTORS = (typeof FinalizationRegistry === "undefined") 205 | ? { register: () => {}, unregister: () => {} } 206 | : new FinalizationRegistry((state) => { 207 | wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b); 208 | }); 209 | 210 | function makeMutClosure(arg0, arg1, dtor, f) { 211 | const state = { a: arg0, b: arg1, cnt: 1, dtor }; 212 | const real = (...args) => { 213 | // First up with a closure we increment the internal reference 214 | // count. This ensures that the Rust closure environment won't 215 | // be deallocated while we're invoking it. 216 | state.cnt++; 217 | const a = state.a; 218 | state.a = 0; 219 | try { 220 | return f(a, state.b, ...args); 221 | } finally { 222 | if (--state.cnt === 0) { 223 | wasm.__wbindgen_export_2.get(state.dtor)(a, state.b); 224 | CLOSURE_DTORS.unregister(state); 225 | } else { 226 | state.a = a; 227 | } 228 | } 229 | }; 230 | real.original = state; 231 | CLOSURE_DTORS.register(real, state, state); 232 | return real; 233 | } 234 | function __wbg_adapter_36(arg0, arg1, arg2) { 235 | wasm 236 | ._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h3530604986fb0653( 237 | arg0, 238 | arg1, 239 | addHeapObject(arg2), 240 | ); 241 | } 242 | 243 | /** 244 | * @param {any} roots 245 | * @param {Function} load 246 | * @param {Function | undefined} [maybe_resolve] 247 | * @returns {Promise} 248 | */ 249 | export function stats(roots, load, maybe_resolve) { 250 | const ret = wasm.stats( 251 | addHeapObject(roots), 252 | addHeapObject(load), 253 | isLikeNone(maybe_resolve) ? 0 : addHeapObject(maybe_resolve), 254 | ); 255 | return takeObject(ret); 256 | } 257 | 258 | function handleError(f, args) { 259 | try { 260 | return f.apply(this, args); 261 | } catch (e) { 262 | wasm.__wbindgen_exn_store(addHeapObject(e)); 263 | } 264 | } 265 | function __wbg_adapter_90(arg0, arg1, arg2, arg3) { 266 | wasm.wasm_bindgen__convert__closures__invoke2_mut__h217775285d50fd50( 267 | arg0, 268 | arg1, 269 | addHeapObject(arg2), 270 | addHeapObject(arg3), 271 | ); 272 | } 273 | 274 | const imports = { 275 | __wbindgen_placeholder__: { 276 | __wbindgen_object_drop_ref: function (arg0) { 277 | takeObject(arg0); 278 | }, 279 | __wbindgen_error_new: function (arg0, arg1) { 280 | const ret = new Error(getStringFromWasm0(arg0, arg1)); 281 | return addHeapObject(ret); 282 | }, 283 | __wbindgen_string_get: function (arg0, arg1) { 284 | const obj = getObject(arg1); 285 | const ret = typeof obj === "string" ? obj : undefined; 286 | var ptr1 = isLikeNone(ret) 287 | ? 0 288 | : passStringToWasm0( 289 | ret, 290 | wasm.__wbindgen_malloc, 291 | wasm.__wbindgen_realloc, 292 | ); 293 | var len1 = WASM_VECTOR_LEN; 294 | getInt32Memory0()[arg0 / 4 + 1] = len1; 295 | getInt32Memory0()[arg0 / 4 + 0] = ptr1; 296 | }, 297 | __wbindgen_string_new: function (arg0, arg1) { 298 | const ret = getStringFromWasm0(arg0, arg1); 299 | return addHeapObject(ret); 300 | }, 301 | __wbindgen_object_clone_ref: function (arg0) { 302 | const ret = getObject(arg0); 303 | return addHeapObject(ret); 304 | }, 305 | __wbindgen_is_object: function (arg0) { 306 | const val = getObject(arg0); 307 | const ret = typeof val === "object" && val !== null; 308 | return ret; 309 | }, 310 | __wbindgen_jsval_loose_eq: function (arg0, arg1) { 311 | const ret = getObject(arg0) == getObject(arg1); 312 | return ret; 313 | }, 314 | __wbindgen_boolean_get: function (arg0) { 315 | const v = getObject(arg0); 316 | const ret = typeof v === "boolean" ? (v ? 1 : 0) : 2; 317 | return ret; 318 | }, 319 | __wbindgen_number_get: function (arg0, arg1) { 320 | const obj = getObject(arg1); 321 | const ret = typeof obj === "number" ? obj : undefined; 322 | getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; 323 | getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret); 324 | }, 325 | __wbg_String_b9412f8799faab3e: function (arg0, arg1) { 326 | const ret = String(getObject(arg1)); 327 | const ptr1 = passStringToWasm0( 328 | ret, 329 | wasm.__wbindgen_malloc, 330 | wasm.__wbindgen_realloc, 331 | ); 332 | const len1 = WASM_VECTOR_LEN; 333 | getInt32Memory0()[arg0 / 4 + 1] = len1; 334 | getInt32Memory0()[arg0 / 4 + 0] = ptr1; 335 | }, 336 | __wbindgen_number_new: function (arg0) { 337 | const ret = arg0; 338 | return addHeapObject(ret); 339 | }, 340 | __wbg_set_f975102236d3c502: function (arg0, arg1, arg2) { 341 | getObject(arg0)[takeObject(arg1)] = takeObject(arg2); 342 | }, 343 | __wbg_queueMicrotask_3cbae2ec6b6cd3d6: function (arg0) { 344 | const ret = getObject(arg0).queueMicrotask; 345 | return addHeapObject(ret); 346 | }, 347 | __wbindgen_is_function: function (arg0) { 348 | const ret = typeof (getObject(arg0)) === "function"; 349 | return ret; 350 | }, 351 | __wbindgen_cb_drop: function (arg0) { 352 | const obj = takeObject(arg0).original; 353 | if (obj.cnt-- == 1) { 354 | obj.a = 0; 355 | return true; 356 | } 357 | const ret = false; 358 | return ret; 359 | }, 360 | __wbg_queueMicrotask_481971b0d87f3dd4: function (arg0) { 361 | queueMicrotask(getObject(arg0)); 362 | }, 363 | __wbindgen_is_string: function (arg0) { 364 | const ret = typeof (getObject(arg0)) === "string"; 365 | return ret; 366 | }, 367 | __wbg_self_ce0dbfc45cf2f5be: function () { 368 | return handleError(function () { 369 | const ret = self.self; 370 | return addHeapObject(ret); 371 | }, arguments); 372 | }, 373 | __wbg_window_c6fb939a7f436783: function () { 374 | return handleError(function () { 375 | const ret = window.window; 376 | return addHeapObject(ret); 377 | }, arguments); 378 | }, 379 | __wbg_globalThis_d1e6af4856ba331b: function () { 380 | return handleError(function () { 381 | const ret = globalThis.globalThis; 382 | return addHeapObject(ret); 383 | }, arguments); 384 | }, 385 | __wbg_global_207b558942527489: function () { 386 | return handleError(function () { 387 | const ret = global.global; 388 | return addHeapObject(ret); 389 | }, arguments); 390 | }, 391 | __wbindgen_is_undefined: function (arg0) { 392 | const ret = getObject(arg0) === undefined; 393 | return ret; 394 | }, 395 | __wbg_newnoargs_e258087cd0daa0ea: function (arg0, arg1) { 396 | const ret = new Function(getStringFromWasm0(arg0, arg1)); 397 | return addHeapObject(ret); 398 | }, 399 | __wbg_call_27c0f87801dedf93: function () { 400 | return handleError(function (arg0, arg1) { 401 | const ret = getObject(arg0).call(getObject(arg1)); 402 | return addHeapObject(ret); 403 | }, arguments); 404 | }, 405 | __wbg_get_bd8e338fbd5f5cc8: function (arg0, arg1) { 406 | const ret = getObject(arg0)[arg1 >>> 0]; 407 | return addHeapObject(ret); 408 | }, 409 | __wbg_length_cd7af8117672b8b8: function (arg0) { 410 | const ret = getObject(arg0).length; 411 | return ret; 412 | }, 413 | __wbg_new_16b304a2cfa7ff4a: function () { 414 | const ret = new Array(); 415 | return addHeapObject(ret); 416 | }, 417 | __wbg_new_d9bc3a0147634640: function () { 418 | const ret = new Map(); 419 | return addHeapObject(ret); 420 | }, 421 | __wbg_next_40fc327bfc8770e6: function (arg0) { 422 | const ret = getObject(arg0).next; 423 | return addHeapObject(ret); 424 | }, 425 | __wbg_next_196c84450b364254: function () { 426 | return handleError(function (arg0) { 427 | const ret = getObject(arg0).next(); 428 | return addHeapObject(ret); 429 | }, arguments); 430 | }, 431 | __wbg_done_298b57d23c0fc80c: function (arg0) { 432 | const ret = getObject(arg0).done; 433 | return ret; 434 | }, 435 | __wbg_value_d93c65011f51a456: function (arg0) { 436 | const ret = getObject(arg0).value; 437 | return addHeapObject(ret); 438 | }, 439 | __wbg_iterator_2cee6dadfd956dfa: function () { 440 | const ret = Symbol.iterator; 441 | return addHeapObject(ret); 442 | }, 443 | __wbg_get_e3c254076557e348: function () { 444 | return handleError(function (arg0, arg1) { 445 | const ret = Reflect.get(getObject(arg0), getObject(arg1)); 446 | return addHeapObject(ret); 447 | }, arguments); 448 | }, 449 | __wbg_new_72fb9a18b5ae2624: function () { 450 | const ret = new Object(); 451 | return addHeapObject(ret); 452 | }, 453 | __wbg_set_d4638f722068f043: function (arg0, arg1, arg2) { 454 | getObject(arg0)[arg1 >>> 0] = takeObject(arg2); 455 | }, 456 | __wbg_isArray_2ab64d95e09ea0ae: function (arg0) { 457 | const ret = Array.isArray(getObject(arg0)); 458 | return ret; 459 | }, 460 | __wbg_instanceof_ArrayBuffer_836825be07d4c9d2: function (arg0) { 461 | let result; 462 | try { 463 | result = getObject(arg0) instanceof ArrayBuffer; 464 | } catch (_) { 465 | result = false; 466 | } 467 | const ret = result; 468 | return ret; 469 | }, 470 | __wbg_message_5bf28016c2b49cfb: function (arg0) { 471 | const ret = getObject(arg0).message; 472 | return addHeapObject(ret); 473 | }, 474 | __wbg_name_e7429f0dda6079e2: function (arg0) { 475 | const ret = getObject(arg0).name; 476 | return addHeapObject(ret); 477 | }, 478 | __wbg_call_b3ca7c6051f9bec1: function () { 479 | return handleError(function (arg0, arg1, arg2) { 480 | const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); 481 | return addHeapObject(ret); 482 | }, arguments); 483 | }, 484 | __wbg_call_8e7cb608789c2528: function () { 485 | return handleError(function (arg0, arg1, arg2, arg3) { 486 | const ret = getObject(arg0).call( 487 | getObject(arg1), 488 | getObject(arg2), 489 | getObject(arg3), 490 | ); 491 | return addHeapObject(ret); 492 | }, arguments); 493 | }, 494 | __wbg_set_8417257aaedc936b: function (arg0, arg1, arg2) { 495 | const ret = getObject(arg0).set(getObject(arg1), getObject(arg2)); 496 | return addHeapObject(ret); 497 | }, 498 | __wbg_buffer_12d079cc21e14bdb: function (arg0) { 499 | const ret = getObject(arg0).buffer; 500 | return addHeapObject(ret); 501 | }, 502 | __wbg_new_81740750da40724f: function (arg0, arg1) { 503 | try { 504 | var state0 = { a: arg0, b: arg1 }; 505 | var cb0 = (arg0, arg1) => { 506 | const a = state0.a; 507 | state0.a = 0; 508 | try { 509 | return __wbg_adapter_90(a, state0.b, arg0, arg1); 510 | } finally { 511 | state0.a = a; 512 | } 513 | }; 514 | const ret = new Promise(cb0); 515 | return addHeapObject(ret); 516 | } finally { 517 | state0.a = state0.b = 0; 518 | } 519 | }, 520 | __wbg_resolve_b0083a7967828ec8: function (arg0) { 521 | const ret = Promise.resolve(getObject(arg0)); 522 | return addHeapObject(ret); 523 | }, 524 | __wbg_then_0c86a60e8fcfe9f6: function (arg0, arg1) { 525 | const ret = getObject(arg0).then(getObject(arg1)); 526 | return addHeapObject(ret); 527 | }, 528 | __wbg_then_a73caa9a87991566: function (arg0, arg1, arg2) { 529 | const ret = getObject(arg0).then(getObject(arg1), getObject(arg2)); 530 | return addHeapObject(ret); 531 | }, 532 | __wbg_new_63b92bc8671ed464: function (arg0) { 533 | const ret = new Uint8Array(getObject(arg0)); 534 | return addHeapObject(ret); 535 | }, 536 | __wbg_set_a47bac70306a19a7: function (arg0, arg1, arg2) { 537 | getObject(arg0).set(getObject(arg1), arg2 >>> 0); 538 | }, 539 | __wbg_length_c20a40f15020d68a: function (arg0) { 540 | const ret = getObject(arg0).length; 541 | return ret; 542 | }, 543 | __wbg_instanceof_Uint8Array_2b3bbecd033d19f6: function (arg0) { 544 | let result; 545 | try { 546 | result = getObject(arg0) instanceof Uint8Array; 547 | } catch (_) { 548 | result = false; 549 | } 550 | const ret = result; 551 | return ret; 552 | }, 553 | __wbindgen_debug_string: function (arg0, arg1) { 554 | const ret = debugString(getObject(arg1)); 555 | const ptr1 = passStringToWasm0( 556 | ret, 557 | wasm.__wbindgen_malloc, 558 | wasm.__wbindgen_realloc, 559 | ); 560 | const len1 = WASM_VECTOR_LEN; 561 | getInt32Memory0()[arg0 / 4 + 1] = len1; 562 | getInt32Memory0()[arg0 / 4 + 0] = ptr1; 563 | }, 564 | __wbindgen_throw: function (arg0, arg1) { 565 | throw new Error(getStringFromWasm0(arg0, arg1)); 566 | }, 567 | __wbindgen_memory: function () { 568 | const ret = wasm.memory; 569 | return addHeapObject(ret); 570 | }, 571 | __wbindgen_closure_wrapper706: function (arg0, arg1, arg2) { 572 | const ret = makeMutClosure(arg0, arg1, 150, __wbg_adapter_36); 573 | return addHeapObject(ret); 574 | }, 575 | }, 576 | }; 577 | 578 | class WasmBuildLoader { 579 | #options; 580 | #lastLoadPromise; 581 | #instantiated; 582 | 583 | constructor(options) { 584 | this.#options = options; 585 | } 586 | 587 | get instance() { 588 | return this.#instantiated?.instance; 589 | } 590 | 591 | get module() { 592 | return this.#instantiated?.module; 593 | } 594 | 595 | load( 596 | url, 597 | decompress, 598 | ) { 599 | if (this.#instantiated) { 600 | return Promise.resolve(this.#instantiated); 601 | } else if (this.#lastLoadPromise == null) { 602 | this.#lastLoadPromise = (async () => { 603 | try { 604 | this.#instantiated = await this.#instantiate(url, decompress); 605 | return this.#instantiated; 606 | } finally { 607 | this.#lastLoadPromise = undefined; 608 | } 609 | })(); 610 | } 611 | return this.#lastLoadPromise; 612 | } 613 | 614 | async #instantiate(url, decompress) { 615 | const imports = this.#options.imports; 616 | if (this.#options.cache != null && url.protocol !== "file:") { 617 | try { 618 | const result = await this.#options.cache( 619 | url, 620 | decompress ?? ((bytes) => bytes), 621 | ); 622 | if (result instanceof URL) { 623 | url = result; 624 | decompress = undefined; // already decompressed 625 | } else if (result != null) { 626 | return WebAssembly.instantiate(result, imports); 627 | } 628 | } catch { 629 | // ignore if caching ever fails (ex. when on deploy) 630 | } 631 | } 632 | 633 | const isFile = url.protocol === "file:"; 634 | 635 | // make file urls work in Node via dnt 636 | const isNode = globalThis.process?.versions?.node != null; 637 | if (isFile && typeof Deno !== "object") { 638 | throw new Error( 639 | "Loading local files are not supported in this environment", 640 | ); 641 | } 642 | if (isNode && isFile) { 643 | // the deno global will be shimmed by dnt 644 | const wasmCode = await Deno.readFile(url); 645 | return WebAssembly.instantiate( 646 | decompress ? decompress(wasmCode) : wasmCode, 647 | imports, 648 | ); 649 | } 650 | 651 | switch (url.protocol) { 652 | case "file:": 653 | case "https:": 654 | case "http:": { 655 | const wasmResponse = await fetchWithRetries(url); 656 | if (decompress) { 657 | const wasmCode = new Uint8Array(await wasmResponse.arrayBuffer()); 658 | return WebAssembly.instantiate(decompress(wasmCode), imports); 659 | } 660 | if ( 661 | isFile || 662 | wasmResponse.headers.get("content-type")?.toLowerCase() 663 | .startsWith("application/wasm") 664 | ) { 665 | return WebAssembly.instantiateStreaming(wasmResponse, imports); 666 | } else { 667 | return WebAssembly.instantiate( 668 | await wasmResponse.arrayBuffer(), 669 | imports, 670 | ); 671 | } 672 | } 673 | default: 674 | throw new Error(`Unsupported protocol: ${url.protocol}`); 675 | } 676 | } 677 | } 678 | const isNodeOrDeno = typeof Deno === "object" || 679 | (typeof process !== "undefined" && process.versions != null && 680 | process.versions.node != null); 681 | 682 | const loader = new WasmBuildLoader({ 683 | imports, 684 | cache: isNodeOrDeno ? cacheToLocalDir : undefined, 685 | }); 686 | 687 | export async function instantiate(opts) { 688 | return (await instantiateWithInstance(opts)).exports; 689 | } 690 | 691 | export async function instantiateWithInstance(opts) { 692 | const { instance } = await loader.load( 693 | opts?.url ?? new URL("nocuous_bg.wasm", import.meta.url), 694 | opts?.decompress, 695 | ); 696 | wasm = wasm ?? instance.exports; 697 | cachedInt32Memory0 = cachedInt32Memory0 ?? new Int32Array(wasm.memory.buffer); 698 | cachedUint8Memory0 = cachedUint8Memory0 ?? new Uint8Array(wasm.memory.buffer); 699 | return { 700 | instance, 701 | exports: getWasmInstanceExports(), 702 | }; 703 | } 704 | 705 | function getWasmInstanceExports() { 706 | return { stats }; 707 | } 708 | 709 | export function isInstantiated() { 710 | return loader.instance != null; 711 | } 712 | export async function cacheToLocalDir(url, decompress) { 713 | const localPath = await getUrlLocalPath(url); 714 | if (localPath == null) { 715 | return undefined; 716 | } 717 | if (!await exists(localPath)) { 718 | const fileBytes = decompress(new Uint8Array(await getUrlBytes(url))); 719 | try { 720 | await Deno.writeFile(localPath, fileBytes); 721 | } catch { 722 | // ignore and return the wasm bytes 723 | return fileBytes; 724 | } 725 | } 726 | return toFileUrl(localPath); 727 | } 728 | async function getUrlLocalPath(url) { 729 | try { 730 | const dataDirPath = await getInitializedLocalDataDirPath(); 731 | const hash = await getUrlHash(url); 732 | return `${dataDirPath}/${hash}.wasm`; 733 | } catch { 734 | return undefined; 735 | } 736 | } 737 | async function getInitializedLocalDataDirPath() { 738 | const dataDir = localDataDir(); 739 | if (dataDir == null) { 740 | throw new Error(`Could not find local data directory.`); 741 | } 742 | const dirPath = `${dataDir}/deno-wasmbuild`; 743 | await ensureDir(dirPath); 744 | return dirPath; 745 | } 746 | async function exists(filePath) { 747 | try { 748 | await Deno.lstat(filePath); 749 | return true; 750 | } catch (error) { 751 | if (error instanceof Deno.errors.NotFound) { 752 | return false; 753 | } 754 | throw error; 755 | } 756 | } 757 | async function ensureDir(dir) { 758 | try { 759 | const fileInfo = await Deno.lstat(dir); 760 | if (!fileInfo.isDirectory) { 761 | throw new Error(`Path was not a directory '${dir}'`); 762 | } 763 | } catch (err) { 764 | if (err instanceof Deno.errors.NotFound) { 765 | // if dir not exists. then create it. 766 | await Deno.mkdir(dir, { recursive: true }); 767 | return; 768 | } 769 | throw err; 770 | } 771 | } 772 | async function getUrlHash(url) { 773 | // Taken from MDN: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest 774 | const hashBuffer = await crypto.subtle.digest( 775 | "SHA-256", 776 | new TextEncoder().encode(url.href), 777 | ); 778 | // convert buffer to byte array 779 | const hashArray = Array.from(new Uint8Array(hashBuffer)); 780 | // convert bytes to hex string 781 | const hashHex = hashArray 782 | .map((b) => b.toString(16).padStart(2, "0")) 783 | .join(""); 784 | return hashHex; 785 | } 786 | async function getUrlBytes(url) { 787 | const response = await fetchWithRetries(url); 788 | return await response.arrayBuffer(); 789 | } 790 | // the below is extracted from deno_std/path 791 | const WHITESPACE_ENCODINGS = { 792 | "\u0009": "%09", 793 | "\u000A": "%0A", 794 | "\u000B": "%0B", 795 | "\u000C": "%0C", 796 | "\u000D": "%0D", 797 | "\u0020": "%20", 798 | }; 799 | function encodeWhitespace(string) { 800 | return string.replaceAll(/[\s]/g, (c) => { 801 | return WHITESPACE_ENCODINGS[c] ?? c; 802 | }); 803 | } 804 | function toFileUrl(path) { 805 | return Deno.build.os === "windows" 806 | ? windowsToFileUrl(path) 807 | : posixToFileUrl(path); 808 | } 809 | function posixToFileUrl(path) { 810 | const url = new URL("file:///"); 811 | url.pathname = encodeWhitespace( 812 | path.replace(/%/g, "%25").replace(/\\/g, "%5C"), 813 | ); 814 | return url; 815 | } 816 | function windowsToFileUrl(path) { 817 | const [, hostname, pathname] = path.match( 818 | /^(?:[/\\]{2}([^/\\]+)(?=[/\\](?:[^/\\]|$)))?(.*)/, 819 | ); 820 | const url = new URL("file:///"); 821 | url.pathname = encodeWhitespace(pathname.replace(/%/g, "%25")); 822 | if (hostname != null && hostname != "localhost") { 823 | url.hostname = hostname; 824 | if (!url.hostname) { 825 | throw new TypeError("Invalid hostname."); 826 | } 827 | } 828 | return url; 829 | } 830 | export async function fetchWithRetries(url, maxRetries = 5) { 831 | let sleepMs = 250; 832 | let iterationCount = 0; 833 | while (true) { 834 | iterationCount++; 835 | try { 836 | const res = await fetch(url); 837 | if (res.ok || iterationCount > maxRetries) { 838 | return res; 839 | } 840 | } catch (err) { 841 | if (iterationCount > maxRetries) { 842 | throw err; 843 | } 844 | } 845 | console.warn(`Failed fetching. Retrying in ${sleepMs}ms...`); 846 | await new Promise((resolve) => setTimeout(resolve, sleepMs)); 847 | sleepMs = Math.min(sleepMs * 2, 10000); 848 | } 849 | } 850 | // MIT License - Copyright (c) justjavac. 851 | // https://github.com/justjavac/deno_dirs/blob/e8c001bbef558f08fd486d444af391729b0b8068/data_local_dir/mod.ts 852 | function localDataDir() { 853 | switch (Deno.build.os) { 854 | case "linux": { 855 | const xdg = Deno.env.get("XDG_DATA_HOME"); 856 | if (xdg) { 857 | return xdg; 858 | } 859 | const home = Deno.env.get("HOME"); 860 | if (home) { 861 | return `${home}/.local/share`; 862 | } 863 | break; 864 | } 865 | case "darwin": { 866 | const home = Deno.env.get("HOME"); 867 | if (home) { 868 | return `${home}/Library/Application Support`; 869 | } 870 | break; 871 | } 872 | case "windows": 873 | return Deno.env.get("LOCALAPPDATA") ?? undefined; 874 | } 875 | return undefined; 876 | } 877 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "Inflector" 7 | version = "0.11.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 10 | dependencies = [ 11 | "lazy_static", 12 | "regex", 13 | ] 14 | 15 | [[package]] 16 | name = "ahash" 17 | version = "0.8.11" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 20 | dependencies = [ 21 | "cfg-if", 22 | "once_cell", 23 | "version_check", 24 | "zerocopy", 25 | ] 26 | 27 | [[package]] 28 | name = "aho-corasick" 29 | version = "1.0.4" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" 32 | dependencies = [ 33 | "memchr", 34 | ] 35 | 36 | [[package]] 37 | name = "allocator-api2" 38 | version = "0.2.18" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 41 | 42 | [[package]] 43 | name = "anyhow" 44 | version = "1.0.75" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 47 | 48 | [[package]] 49 | name = "ast_node" 50 | version = "0.9.9" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "f9184f2b369b3e8625712493c89b785881f27eedc6cde480a81883cef78868b2" 53 | dependencies = [ 54 | "proc-macro2", 55 | "quote", 56 | "swc_macros_common", 57 | "syn 2.0.79", 58 | ] 59 | 60 | [[package]] 61 | name = "autocfg" 62 | version = "1.1.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 65 | 66 | [[package]] 67 | name = "better_scoped_tls" 68 | version = "0.1.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "794edcc9b3fb07bb4aecaa11f093fd45663b4feadb782d68303a2268bc2701de" 71 | dependencies = [ 72 | "scoped-tls", 73 | ] 74 | 75 | [[package]] 76 | name = "bitflags" 77 | version = "2.6.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 80 | 81 | [[package]] 82 | name = "bumpalo" 83 | version = "3.16.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 86 | dependencies = [ 87 | "allocator-api2", 88 | ] 89 | 90 | [[package]] 91 | name = "cc" 92 | version = "1.0.83" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 95 | dependencies = [ 96 | "libc", 97 | ] 98 | 99 | [[package]] 100 | name = "cfg-if" 101 | version = "1.0.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 104 | 105 | [[package]] 106 | name = "data-url" 107 | version = "0.3.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" 110 | 111 | [[package]] 112 | name = "deno_ast" 113 | version = "0.42.1" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "89ea2fd038c9c7e3e87e624fd708303cd33f39c33707f6c48fa9a65d65fefc47" 116 | dependencies = [ 117 | "deno_media_type", 118 | "deno_terminal", 119 | "dprint-swc-ext", 120 | "once_cell", 121 | "percent-encoding", 122 | "serde", 123 | "swc_atoms", 124 | "swc_common", 125 | "swc_ecma_ast", 126 | "swc_ecma_parser", 127 | "swc_ecma_utils", 128 | "swc_ecma_visit", 129 | "swc_eq_ignore_macros", 130 | "swc_macros_common", 131 | "swc_visit", 132 | "swc_visit_macros", 133 | "text_lines", 134 | "thiserror", 135 | "unicode-width", 136 | "url", 137 | ] 138 | 139 | [[package]] 140 | name = "deno_media_type" 141 | version = "0.1.4" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "a8978229b82552bf8457a0125aa20863f023619cfc21ebb007b1e571d68fd85b" 144 | dependencies = [ 145 | "data-url", 146 | "serde", 147 | "url", 148 | ] 149 | 150 | [[package]] 151 | name = "deno_terminal" 152 | version = "0.1.1" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "7e6337d4e7f375f8b986409a76fbeecfa4bd8a1343e63355729ae4befa058eaf" 155 | dependencies = [ 156 | "once_cell", 157 | "termcolor", 158 | ] 159 | 160 | [[package]] 161 | name = "dprint-swc-ext" 162 | version = "0.20.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "0ba28c12892aadb751c2ba7001d8460faee4748a04b4edc51c7121cc67ee03db" 165 | dependencies = [ 166 | "allocator-api2", 167 | "bumpalo", 168 | "num-bigint", 169 | "rustc-hash", 170 | "swc_atoms", 171 | "swc_common", 172 | "swc_ecma_ast", 173 | "swc_ecma_parser", 174 | "text_lines", 175 | ] 176 | 177 | [[package]] 178 | name = "either" 179 | version = "1.13.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 182 | 183 | [[package]] 184 | name = "equivalent" 185 | version = "1.0.1" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 188 | 189 | [[package]] 190 | name = "form_urlencoded" 191 | version = "1.2.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 194 | dependencies = [ 195 | "percent-encoding", 196 | ] 197 | 198 | [[package]] 199 | name = "from_variant" 200 | version = "0.1.9" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4" 203 | dependencies = [ 204 | "proc-macro2", 205 | "swc_macros_common", 206 | "syn 2.0.79", 207 | ] 208 | 209 | [[package]] 210 | name = "futures" 211 | version = "0.3.28" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 214 | dependencies = [ 215 | "futures-channel", 216 | "futures-core", 217 | "futures-executor", 218 | "futures-io", 219 | "futures-sink", 220 | "futures-task", 221 | "futures-util", 222 | ] 223 | 224 | [[package]] 225 | name = "futures-channel" 226 | version = "0.3.28" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 229 | dependencies = [ 230 | "futures-core", 231 | "futures-sink", 232 | ] 233 | 234 | [[package]] 235 | name = "futures-core" 236 | version = "0.3.28" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 239 | 240 | [[package]] 241 | name = "futures-executor" 242 | version = "0.3.28" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 245 | dependencies = [ 246 | "futures-core", 247 | "futures-task", 248 | "futures-util", 249 | ] 250 | 251 | [[package]] 252 | name = "futures-io" 253 | version = "0.3.28" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 256 | 257 | [[package]] 258 | name = "futures-macro" 259 | version = "0.3.28" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 262 | dependencies = [ 263 | "proc-macro2", 264 | "quote", 265 | "syn 2.0.79", 266 | ] 267 | 268 | [[package]] 269 | name = "futures-sink" 270 | version = "0.3.28" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 273 | 274 | [[package]] 275 | name = "futures-task" 276 | version = "0.3.28" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 279 | 280 | [[package]] 281 | name = "futures-util" 282 | version = "0.3.28" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 285 | dependencies = [ 286 | "futures-channel", 287 | "futures-core", 288 | "futures-io", 289 | "futures-macro", 290 | "futures-sink", 291 | "futures-task", 292 | "memchr", 293 | "pin-project-lite", 294 | "pin-utils", 295 | "slab", 296 | ] 297 | 298 | [[package]] 299 | name = "hashbrown" 300 | version = "0.14.5" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 303 | dependencies = [ 304 | "ahash", 305 | "allocator-api2", 306 | ] 307 | 308 | [[package]] 309 | name = "hashbrown" 310 | version = "0.15.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 313 | 314 | [[package]] 315 | name = "hermit-abi" 316 | version = "0.3.9" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 319 | 320 | [[package]] 321 | name = "hstr" 322 | version = "0.2.12" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "dae404c0c5d4e95d4858876ab02eecd6a196bb8caa42050dfa809938833fc412" 325 | dependencies = [ 326 | "hashbrown 0.14.5", 327 | "new_debug_unreachable", 328 | "once_cell", 329 | "phf", 330 | "rustc-hash", 331 | "triomphe", 332 | ] 333 | 334 | [[package]] 335 | name = "idna" 336 | version = "0.4.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 339 | dependencies = [ 340 | "unicode-bidi", 341 | "unicode-normalization", 342 | ] 343 | 344 | [[package]] 345 | name = "import_map" 346 | version = "0.20.1" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "351a787decc56f38d65d16d32687265045d6d6a4531b4a0e1b649def3590354e" 349 | dependencies = [ 350 | "indexmap", 351 | "log", 352 | "percent-encoding", 353 | "serde", 354 | "serde_json", 355 | "thiserror", 356 | "url", 357 | ] 358 | 359 | [[package]] 360 | name = "indexmap" 361 | version = "2.6.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 364 | dependencies = [ 365 | "equivalent", 366 | "hashbrown 0.15.0", 367 | "serde", 368 | ] 369 | 370 | [[package]] 371 | name = "is-macro" 372 | version = "0.3.6" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "2069faacbe981460232f880d26bf3c7634e322d49053aa48c27e3ae642f728f1" 375 | dependencies = [ 376 | "Inflector", 377 | "proc-macro2", 378 | "quote", 379 | "syn 2.0.79", 380 | ] 381 | 382 | [[package]] 383 | name = "itoa" 384 | version = "1.0.4" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" 387 | 388 | [[package]] 389 | name = "js-sys" 390 | version = "0.3.69" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 393 | dependencies = [ 394 | "wasm-bindgen", 395 | ] 396 | 397 | [[package]] 398 | name = "lazy_static" 399 | version = "1.4.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 402 | 403 | [[package]] 404 | name = "libc" 405 | version = "0.2.137" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" 408 | 409 | [[package]] 410 | name = "log" 411 | version = "0.4.17" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 414 | dependencies = [ 415 | "cfg-if", 416 | ] 417 | 418 | [[package]] 419 | name = "memchr" 420 | version = "2.5.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 423 | 424 | [[package]] 425 | name = "new_debug_unreachable" 426 | version = "1.0.6" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 429 | 430 | [[package]] 431 | name = "nocuous" 432 | version = "1.1.0" 433 | dependencies = [ 434 | "anyhow", 435 | "deno_ast", 436 | "futures", 437 | "import_map", 438 | "indexmap", 439 | "js-sys", 440 | "lazy_static", 441 | "regex", 442 | "serde", 443 | "serde-wasm-bindgen", 444 | "url", 445 | "wasm-bindgen", 446 | "wasm-bindgen-futures", 447 | ] 448 | 449 | [[package]] 450 | name = "num-bigint" 451 | version = "0.4.3" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 454 | dependencies = [ 455 | "autocfg", 456 | "num-integer", 457 | "num-traits", 458 | "serde", 459 | ] 460 | 461 | [[package]] 462 | name = "num-integer" 463 | version = "0.1.45" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 466 | dependencies = [ 467 | "autocfg", 468 | "num-traits", 469 | ] 470 | 471 | [[package]] 472 | name = "num-traits" 473 | version = "0.2.15" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 476 | dependencies = [ 477 | "autocfg", 478 | ] 479 | 480 | [[package]] 481 | name = "num_cpus" 482 | version = "1.16.0" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 485 | dependencies = [ 486 | "hermit-abi", 487 | "libc", 488 | ] 489 | 490 | [[package]] 491 | name = "once_cell" 492 | version = "1.20.2" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 495 | 496 | [[package]] 497 | name = "percent-encoding" 498 | version = "2.3.1" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 501 | 502 | [[package]] 503 | name = "phf" 504 | version = "0.11.2" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 507 | dependencies = [ 508 | "phf_macros", 509 | "phf_shared", 510 | ] 511 | 512 | [[package]] 513 | name = "phf_generator" 514 | version = "0.11.2" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" 517 | dependencies = [ 518 | "phf_shared", 519 | "rand", 520 | ] 521 | 522 | [[package]] 523 | name = "phf_macros" 524 | version = "0.11.2" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" 527 | dependencies = [ 528 | "phf_generator", 529 | "phf_shared", 530 | "proc-macro2", 531 | "quote", 532 | "syn 2.0.79", 533 | ] 534 | 535 | [[package]] 536 | name = "phf_shared" 537 | version = "0.11.2" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 540 | dependencies = [ 541 | "siphasher", 542 | ] 543 | 544 | [[package]] 545 | name = "pin-project-lite" 546 | version = "0.2.9" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 549 | 550 | [[package]] 551 | name = "pin-utils" 552 | version = "0.1.0" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 555 | 556 | [[package]] 557 | name = "proc-macro2" 558 | version = "1.0.86" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 561 | dependencies = [ 562 | "unicode-ident", 563 | ] 564 | 565 | [[package]] 566 | name = "psm" 567 | version = "0.1.21" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" 570 | dependencies = [ 571 | "cc", 572 | ] 573 | 574 | [[package]] 575 | name = "ptr_meta" 576 | version = "0.1.4" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" 579 | dependencies = [ 580 | "ptr_meta_derive", 581 | ] 582 | 583 | [[package]] 584 | name = "ptr_meta_derive" 585 | version = "0.1.4" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" 588 | dependencies = [ 589 | "proc-macro2", 590 | "quote", 591 | "syn 1.0.103", 592 | ] 593 | 594 | [[package]] 595 | name = "quote" 596 | version = "1.0.37" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 599 | dependencies = [ 600 | "proc-macro2", 601 | ] 602 | 603 | [[package]] 604 | name = "rand" 605 | version = "0.8.5" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 608 | dependencies = [ 609 | "rand_core", 610 | ] 611 | 612 | [[package]] 613 | name = "rand_core" 614 | version = "0.6.4" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 617 | 618 | [[package]] 619 | name = "regex" 620 | version = "1.9.4" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" 623 | dependencies = [ 624 | "aho-corasick", 625 | "memchr", 626 | "regex-automata", 627 | "regex-syntax", 628 | ] 629 | 630 | [[package]] 631 | name = "regex-automata" 632 | version = "0.3.7" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" 635 | dependencies = [ 636 | "aho-corasick", 637 | "memchr", 638 | "regex-syntax", 639 | ] 640 | 641 | [[package]] 642 | name = "regex-syntax" 643 | version = "0.7.5" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" 646 | 647 | [[package]] 648 | name = "rustc-hash" 649 | version = "1.1.0" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 652 | 653 | [[package]] 654 | name = "ryu" 655 | version = "1.0.11" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 658 | 659 | [[package]] 660 | name = "ryu-js" 661 | version = "1.0.1" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "ad97d4ce1560a5e27cec89519dc8300d1aa6035b099821261c651486a19e44d5" 664 | 665 | [[package]] 666 | name = "scoped-tls" 667 | version = "1.0.1" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 670 | 671 | [[package]] 672 | name = "serde" 673 | version = "1.0.210" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 676 | dependencies = [ 677 | "serde_derive", 678 | ] 679 | 680 | [[package]] 681 | name = "serde-wasm-bindgen" 682 | version = "0.6.5" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" 685 | dependencies = [ 686 | "js-sys", 687 | "serde", 688 | "wasm-bindgen", 689 | ] 690 | 691 | [[package]] 692 | name = "serde_derive" 693 | version = "1.0.210" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 696 | dependencies = [ 697 | "proc-macro2", 698 | "quote", 699 | "syn 2.0.79", 700 | ] 701 | 702 | [[package]] 703 | name = "serde_json" 704 | version = "1.0.128" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 707 | dependencies = [ 708 | "indexmap", 709 | "itoa", 710 | "memchr", 711 | "ryu", 712 | "serde", 713 | ] 714 | 715 | [[package]] 716 | name = "siphasher" 717 | version = "0.3.10" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 720 | 721 | [[package]] 722 | name = "slab" 723 | version = "0.4.7" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 726 | dependencies = [ 727 | "autocfg", 728 | ] 729 | 730 | [[package]] 731 | name = "smallvec" 732 | version = "1.10.0" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 735 | 736 | [[package]] 737 | name = "smartstring" 738 | version = "1.0.1" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" 741 | dependencies = [ 742 | "autocfg", 743 | "static_assertions", 744 | "version_check", 745 | ] 746 | 747 | [[package]] 748 | name = "stable_deref_trait" 749 | version = "1.2.0" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 752 | 753 | [[package]] 754 | name = "stacker" 755 | version = "0.1.15" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" 758 | dependencies = [ 759 | "cc", 760 | "cfg-if", 761 | "libc", 762 | "psm", 763 | "winapi", 764 | ] 765 | 766 | [[package]] 767 | name = "static_assertions" 768 | version = "1.1.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 771 | 772 | [[package]] 773 | name = "string_enum" 774 | version = "0.4.4" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "05e383308aebc257e7d7920224fa055c632478d92744eca77f99be8fa1545b90" 777 | dependencies = [ 778 | "proc-macro2", 779 | "quote", 780 | "swc_macros_common", 781 | "syn 2.0.79", 782 | ] 783 | 784 | [[package]] 785 | name = "swc_allocator" 786 | version = "0.1.9" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "7016ee7a5186d6a80e381aa1926e0f3c7b06eaf444745ff7af3632e978eb8dc5" 789 | dependencies = [ 790 | "bumpalo", 791 | "hashbrown 0.14.5", 792 | "ptr_meta", 793 | "rustc-hash", 794 | "triomphe", 795 | ] 796 | 797 | [[package]] 798 | name = "swc_atoms" 799 | version = "0.6.7" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "bb6567e4e67485b3e7662b486f1565bdae54bd5b9d6b16b2ba1a9babb1e42125" 802 | dependencies = [ 803 | "hstr", 804 | "once_cell", 805 | "rustc-hash", 806 | "serde", 807 | ] 808 | 809 | [[package]] 810 | name = "swc_common" 811 | version = "0.37.5" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "12d0a8eaaf1606c9207077d75828008cb2dfb51b095a766bd2b72ef893576e31" 814 | dependencies = [ 815 | "ast_node", 816 | "better_scoped_tls", 817 | "cfg-if", 818 | "either", 819 | "from_variant", 820 | "new_debug_unreachable", 821 | "num-bigint", 822 | "once_cell", 823 | "rustc-hash", 824 | "serde", 825 | "siphasher", 826 | "swc_allocator", 827 | "swc_atoms", 828 | "swc_eq_ignore_macros", 829 | "swc_visit", 830 | "tracing", 831 | "unicode-width", 832 | "url", 833 | ] 834 | 835 | [[package]] 836 | name = "swc_ecma_ast" 837 | version = "0.118.2" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "a6f866d12e4d519052b92a0a86d1ac7ff17570da1272ca0c89b3d6f802cd79df" 840 | dependencies = [ 841 | "bitflags", 842 | "is-macro", 843 | "num-bigint", 844 | "phf", 845 | "scoped-tls", 846 | "serde", 847 | "string_enum", 848 | "swc_atoms", 849 | "swc_common", 850 | "unicode-id-start", 851 | ] 852 | 853 | [[package]] 854 | name = "swc_ecma_parser" 855 | version = "0.149.1" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "683dada14722714588b56481399c699378b35b2ba4deb5c4db2fb627a97fb54b" 858 | dependencies = [ 859 | "either", 860 | "new_debug_unreachable", 861 | "num-bigint", 862 | "num-traits", 863 | "phf", 864 | "serde", 865 | "smallvec", 866 | "smartstring", 867 | "stacker", 868 | "swc_atoms", 869 | "swc_common", 870 | "swc_ecma_ast", 871 | "tracing", 872 | "typed-arena", 873 | ] 874 | 875 | [[package]] 876 | name = "swc_ecma_utils" 877 | version = "0.134.2" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "029eec7dd485923a75b5a45befd04510288870250270292fc2c1b3a9e7547408" 880 | dependencies = [ 881 | "indexmap", 882 | "num_cpus", 883 | "once_cell", 884 | "rustc-hash", 885 | "ryu-js", 886 | "swc_atoms", 887 | "swc_common", 888 | "swc_ecma_ast", 889 | "swc_ecma_visit", 890 | "tracing", 891 | "unicode-id", 892 | ] 893 | 894 | [[package]] 895 | name = "swc_ecma_visit" 896 | version = "0.104.8" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "5b1c6802e68e51f336e8bc9644e9ff9da75d7da9c1a6247d532f2e908aa33e81" 899 | dependencies = [ 900 | "new_debug_unreachable", 901 | "num-bigint", 902 | "swc_atoms", 903 | "swc_common", 904 | "swc_ecma_ast", 905 | "swc_visit", 906 | "tracing", 907 | ] 908 | 909 | [[package]] 910 | name = "swc_eq_ignore_macros" 911 | version = "0.1.4" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "63db0adcff29d220c3d151c5b25c0eabe7e32dd936212b84cdaa1392e3130497" 914 | dependencies = [ 915 | "proc-macro2", 916 | "quote", 917 | "syn 2.0.79", 918 | ] 919 | 920 | [[package]] 921 | name = "swc_macros_common" 922 | version = "0.3.13" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847" 925 | dependencies = [ 926 | "proc-macro2", 927 | "quote", 928 | "syn 2.0.79", 929 | ] 930 | 931 | [[package]] 932 | name = "swc_visit" 933 | version = "0.6.2" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "1ceb044142ba2719ef9eb3b6b454fce61ab849eb696c34d190f04651955c613d" 936 | dependencies = [ 937 | "either", 938 | "new_debug_unreachable", 939 | ] 940 | 941 | [[package]] 942 | name = "swc_visit_macros" 943 | version = "0.5.13" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "92807d840959f39c60ce8a774a3f83e8193c658068e6d270dbe0a05e40e90b41" 946 | dependencies = [ 947 | "Inflector", 948 | "proc-macro2", 949 | "quote", 950 | "swc_macros_common", 951 | "syn 2.0.79", 952 | ] 953 | 954 | [[package]] 955 | name = "syn" 956 | version = "1.0.103" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 959 | dependencies = [ 960 | "proc-macro2", 961 | "quote", 962 | "unicode-ident", 963 | ] 964 | 965 | [[package]] 966 | name = "syn" 967 | version = "2.0.79" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 970 | dependencies = [ 971 | "proc-macro2", 972 | "quote", 973 | "unicode-ident", 974 | ] 975 | 976 | [[package]] 977 | name = "termcolor" 978 | version = "1.4.1" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 981 | dependencies = [ 982 | "winapi-util", 983 | ] 984 | 985 | [[package]] 986 | name = "text_lines" 987 | version = "0.6.0" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "7fd5828de7deaa782e1dd713006ae96b3bee32d3279b79eb67ecf8072c059bcf" 990 | dependencies = [ 991 | "serde", 992 | ] 993 | 994 | [[package]] 995 | name = "thiserror" 996 | version = "1.0.64" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 999 | dependencies = [ 1000 | "thiserror-impl", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "thiserror-impl" 1005 | version = "1.0.64" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 1008 | dependencies = [ 1009 | "proc-macro2", 1010 | "quote", 1011 | "syn 2.0.79", 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "tinyvec" 1016 | version = "1.6.0" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1019 | dependencies = [ 1020 | "tinyvec_macros", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "tinyvec_macros" 1025 | version = "0.1.0" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1028 | 1029 | [[package]] 1030 | name = "tracing" 1031 | version = "0.1.40" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1034 | dependencies = [ 1035 | "pin-project-lite", 1036 | "tracing-attributes", 1037 | "tracing-core", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "tracing-attributes" 1042 | version = "0.1.27" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1045 | dependencies = [ 1046 | "proc-macro2", 1047 | "quote", 1048 | "syn 2.0.79", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "tracing-core" 1053 | version = "0.1.32" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1056 | dependencies = [ 1057 | "once_cell", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "triomphe" 1062 | version = "0.1.14" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" 1065 | dependencies = [ 1066 | "serde", 1067 | "stable_deref_trait", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "typed-arena" 1072 | version = "2.0.1" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" 1075 | 1076 | [[package]] 1077 | name = "unicode-bidi" 1078 | version = "0.3.13" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 1081 | 1082 | [[package]] 1083 | name = "unicode-id" 1084 | version = "0.3.3" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" 1087 | 1088 | [[package]] 1089 | name = "unicode-id-start" 1090 | version = "1.3.0" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "97e2a3c5fc9de285c0e805d98eba666adb4b2d9e1049ce44821ff7707cc34e91" 1093 | 1094 | [[package]] 1095 | name = "unicode-ident" 1096 | version = "1.0.5" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 1099 | 1100 | [[package]] 1101 | name = "unicode-normalization" 1102 | version = "0.1.22" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1105 | dependencies = [ 1106 | "tinyvec", 1107 | ] 1108 | 1109 | [[package]] 1110 | name = "unicode-width" 1111 | version = "0.1.14" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1114 | 1115 | [[package]] 1116 | name = "url" 1117 | version = "2.4.1" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 1120 | dependencies = [ 1121 | "form_urlencoded", 1122 | "idna", 1123 | "percent-encoding", 1124 | "serde", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "version_check" 1129 | version = "0.9.4" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1132 | 1133 | [[package]] 1134 | name = "wasm-bindgen" 1135 | version = "0.2.92" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 1138 | dependencies = [ 1139 | "cfg-if", 1140 | "serde", 1141 | "serde_json", 1142 | "wasm-bindgen-macro", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "wasm-bindgen-backend" 1147 | version = "0.2.92" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 1150 | dependencies = [ 1151 | "bumpalo", 1152 | "log", 1153 | "once_cell", 1154 | "proc-macro2", 1155 | "quote", 1156 | "syn 2.0.79", 1157 | "wasm-bindgen-shared", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "wasm-bindgen-futures" 1162 | version = "0.4.42" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" 1165 | dependencies = [ 1166 | "cfg-if", 1167 | "js-sys", 1168 | "wasm-bindgen", 1169 | "web-sys", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "wasm-bindgen-macro" 1174 | version = "0.2.92" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 1177 | dependencies = [ 1178 | "quote", 1179 | "wasm-bindgen-macro-support", 1180 | ] 1181 | 1182 | [[package]] 1183 | name = "wasm-bindgen-macro-support" 1184 | version = "0.2.92" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 1187 | dependencies = [ 1188 | "proc-macro2", 1189 | "quote", 1190 | "syn 2.0.79", 1191 | "wasm-bindgen-backend", 1192 | "wasm-bindgen-shared", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "wasm-bindgen-shared" 1197 | version = "0.2.92" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 1200 | 1201 | [[package]] 1202 | name = "web-sys" 1203 | version = "0.3.60" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" 1206 | dependencies = [ 1207 | "js-sys", 1208 | "wasm-bindgen", 1209 | ] 1210 | 1211 | [[package]] 1212 | name = "winapi" 1213 | version = "0.3.9" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1216 | dependencies = [ 1217 | "winapi-i686-pc-windows-gnu", 1218 | "winapi-x86_64-pc-windows-gnu", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "winapi-i686-pc-windows-gnu" 1223 | version = "0.4.0" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1226 | 1227 | [[package]] 1228 | name = "winapi-util" 1229 | version = "0.1.9" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1232 | dependencies = [ 1233 | "windows-sys", 1234 | ] 1235 | 1236 | [[package]] 1237 | name = "winapi-x86_64-pc-windows-gnu" 1238 | version = "0.4.0" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1241 | 1242 | [[package]] 1243 | name = "windows-sys" 1244 | version = "0.59.0" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1247 | dependencies = [ 1248 | "windows-targets", 1249 | ] 1250 | 1251 | [[package]] 1252 | name = "windows-targets" 1253 | version = "0.52.6" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1256 | dependencies = [ 1257 | "windows_aarch64_gnullvm", 1258 | "windows_aarch64_msvc", 1259 | "windows_i686_gnu", 1260 | "windows_i686_gnullvm", 1261 | "windows_i686_msvc", 1262 | "windows_x86_64_gnu", 1263 | "windows_x86_64_gnullvm", 1264 | "windows_x86_64_msvc", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "windows_aarch64_gnullvm" 1269 | version = "0.52.6" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1272 | 1273 | [[package]] 1274 | name = "windows_aarch64_msvc" 1275 | version = "0.52.6" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1278 | 1279 | [[package]] 1280 | name = "windows_i686_gnu" 1281 | version = "0.52.6" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1284 | 1285 | [[package]] 1286 | name = "windows_i686_gnullvm" 1287 | version = "0.52.6" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1290 | 1291 | [[package]] 1292 | name = "windows_i686_msvc" 1293 | version = "0.52.6" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1296 | 1297 | [[package]] 1298 | name = "windows_x86_64_gnu" 1299 | version = "0.52.6" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1302 | 1303 | [[package]] 1304 | name = "windows_x86_64_gnullvm" 1305 | version = "0.52.6" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1308 | 1309 | [[package]] 1310 | name = "windows_x86_64_msvc" 1311 | version = "0.52.6" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1314 | 1315 | [[package]] 1316 | name = "zerocopy" 1317 | version = "0.7.35" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1320 | dependencies = [ 1321 | "zerocopy-derive", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "zerocopy-derive" 1326 | version = "0.7.35" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1329 | dependencies = [ 1330 | "proc-macro2", 1331 | "quote", 1332 | "syn 2.0.79", 1333 | ] 1334 | --------------------------------------------------------------------------------