@tishlang/tish-format 1.0.12 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/Cargo.toml +51 -0
  2. package/LICENSE +13 -0
  3. package/bin/tish-format +0 -0
  4. package/crates/js_to_tish/Cargo.toml +11 -0
  5. package/crates/js_to_tish/README.md +18 -0
  6. package/crates/js_to_tish/src/error.rs +55 -0
  7. package/crates/js_to_tish/src/lib.rs +11 -0
  8. package/crates/js_to_tish/src/span_util.rs +35 -0
  9. package/crates/js_to_tish/src/transform/expr.rs +611 -0
  10. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  11. package/crates/js_to_tish/src/transform.rs +60 -0
  12. package/crates/tish/Cargo.toml +62 -0
  13. package/crates/tish/build.rs +21 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +576 -0
  16. package/crates/tish/src/main.rs +853 -0
  17. package/crates/tish/src/repl_completion.rs +199 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/error_source_location.rs +36 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  24. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  25. package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
  26. package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
  27. package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
  28. package/crates/tish/tests/integration_test.rs +1406 -0
  29. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  30. package/crates/tish/tests/shortcircuit.rs +65 -0
  31. package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
  32. package/crates/tish/tests/tty_capability.rs +43 -0
  33. package/crates/tish_ast/Cargo.toml +9 -0
  34. package/crates/tish_ast/src/ast.rs +649 -0
  35. package/crates/tish_ast/src/lib.rs +5 -0
  36. package/crates/tish_build_utils/Cargo.toml +11 -0
  37. package/crates/tish_build_utils/src/lib.rs +577 -0
  38. package/crates/tish_builtins/Cargo.toml +22 -0
  39. package/crates/tish_builtins/src/array.rs +803 -0
  40. package/crates/tish_builtins/src/collections.rs +481 -0
  41. package/crates/tish_builtins/src/construct.rs +199 -0
  42. package/crates/tish_builtins/src/date.rs +538 -0
  43. package/crates/tish_builtins/src/globals.rs +293 -0
  44. package/crates/tish_builtins/src/helpers.rs +35 -0
  45. package/crates/tish_builtins/src/iterator.rs +129 -0
  46. package/crates/tish_builtins/src/lib.rs +21 -0
  47. package/crates/tish_builtins/src/math.rs +89 -0
  48. package/crates/tish_builtins/src/number.rs +96 -0
  49. package/crates/tish_builtins/src/object.rs +36 -0
  50. package/crates/tish_builtins/src/string.rs +646 -0
  51. package/crates/tish_builtins/src/symbol.rs +83 -0
  52. package/crates/tish_builtins/src/typedarrays.rs +298 -0
  53. package/crates/tish_bytecode/Cargo.toml +17 -0
  54. package/crates/tish_bytecode/src/chunk.rs +164 -0
  55. package/crates/tish_bytecode/src/compiler.rs +2604 -0
  56. package/crates/tish_bytecode/src/encoding.rs +102 -0
  57. package/crates/tish_bytecode/src/lib.rs +20 -0
  58. package/crates/tish_bytecode/src/opcode.rs +185 -0
  59. package/crates/tish_bytecode/src/peephole.rs +189 -0
  60. package/crates/tish_bytecode/src/serialize.rs +193 -0
  61. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  62. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  63. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  64. package/crates/tish_compile/Cargo.toml +27 -0
  65. package/crates/tish_compile/src/check.rs +774 -0
  66. package/crates/tish_compile/src/codegen.rs +7317 -0
  67. package/crates/tish_compile/src/infer.rs +1681 -0
  68. package/crates/tish_compile/src/lib.rs +206 -0
  69. package/crates/tish_compile/src/resolve.rs +1951 -0
  70. package/crates/tish_compile/src/types.rs +605 -0
  71. package/crates/tish_compile_js/Cargo.toml +18 -0
  72. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  73. package/crates/tish_compile_js/src/codegen.rs +938 -0
  74. package/crates/tish_compile_js/src/error.rs +20 -0
  75. package/crates/tish_compile_js/src/lib.rs +26 -0
  76. package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
  77. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  78. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  79. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  80. package/crates/tish_core/Cargo.toml +32 -0
  81. package/crates/tish_core/src/console_style.rs +170 -0
  82. package/crates/tish_core/src/json.rs +430 -0
  83. package/crates/tish_core/src/lib.rs +20 -0
  84. package/crates/tish_core/src/macros.rs +36 -0
  85. package/crates/tish_core/src/shape.rs +85 -0
  86. package/crates/tish_core/src/uri.rs +118 -0
  87. package/crates/tish_core/src/value.rs +1350 -0
  88. package/crates/tish_core/src/vmref.rs +183 -0
  89. package/crates/tish_cranelift/Cargo.toml +19 -0
  90. package/crates/tish_cranelift/src/lib.rs +43 -0
  91. package/crates/tish_cranelift/src/link.rs +130 -0
  92. package/crates/tish_cranelift/src/lower.rs +85 -0
  93. package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
  94. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  95. package/crates/tish_eval/Cargo.toml +51 -0
  96. package/crates/tish_eval/src/eval.rs +4265 -0
  97. package/crates/tish_eval/src/http.rs +191 -0
  98. package/crates/tish_eval/src/lib.rs +99 -0
  99. package/crates/tish_eval/src/natives.rs +551 -0
  100. package/crates/tish_eval/src/promise.rs +179 -0
  101. package/crates/tish_eval/src/regex.rs +299 -0
  102. package/crates/tish_eval/src/timers.rs +120 -0
  103. package/crates/tish_eval/src/value.rs +336 -0
  104. package/crates/tish_eval/src/value_convert.rs +117 -0
  105. package/crates/tish_ffi/Cargo.toml +26 -0
  106. package/crates/tish_ffi/src/lib.rs +518 -0
  107. package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
  108. package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
  109. package/crates/tish_ffi/tests/loader.rs +65 -0
  110. package/crates/tish_fmt/Cargo.toml +16 -0
  111. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  112. package/crates/tish_fmt/src/lib.rs +2157 -0
  113. package/crates/tish_jsx_web/Cargo.toml +9 -0
  114. package/crates/tish_jsx_web/README.md +5 -0
  115. package/crates/tish_jsx_web/src/lib.rs +2 -0
  116. package/crates/tish_lexer/Cargo.toml +9 -0
  117. package/crates/tish_lexer/src/lib.rs +1104 -0
  118. package/crates/tish_lexer/src/token.rs +170 -0
  119. package/crates/tish_lint/Cargo.toml +18 -0
  120. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  121. package/crates/tish_lint/src/lib.rs +281 -0
  122. package/crates/tish_llvm/Cargo.toml +13 -0
  123. package/crates/tish_llvm/src/lib.rs +115 -0
  124. package/crates/tish_lsp/Cargo.toml +25 -0
  125. package/crates/tish_lsp/README.md +26 -0
  126. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  127. package/crates/tish_lsp/src/import_goto.rs +564 -0
  128. package/crates/tish_lsp/src/main.rs +1459 -0
  129. package/crates/tish_native/Cargo.toml +16 -0
  130. package/crates/tish_native/src/build.rs +481 -0
  131. package/crates/tish_native/src/config.rs +48 -0
  132. package/crates/tish_native/src/lib.rs +416 -0
  133. package/crates/tish_opt/Cargo.toml +13 -0
  134. package/crates/tish_opt/src/lib.rs +1046 -0
  135. package/crates/tish_parser/Cargo.toml +11 -0
  136. package/crates/tish_parser/src/lib.rs +386 -0
  137. package/crates/tish_parser/src/parser.rs +2726 -0
  138. package/crates/tish_pg/Cargo.toml +34 -0
  139. package/crates/tish_pg/README.md +38 -0
  140. package/crates/tish_pg/src/error.rs +52 -0
  141. package/crates/tish_pg/src/lib.rs +955 -0
  142. package/crates/tish_resolve/Cargo.toml +13 -0
  143. package/crates/tish_resolve/src/lib.rs +3601 -0
  144. package/crates/tish_resolve/src/pos.rs +141 -0
  145. package/crates/tish_runtime/Cargo.toml +100 -0
  146. package/crates/tish_runtime/src/http.rs +1347 -0
  147. package/crates/tish_runtime/src/http_fetch.rs +492 -0
  148. package/crates/tish_runtime/src/http_hyper.rs +441 -0
  149. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  150. package/crates/tish_runtime/src/lib.rs +1447 -0
  151. package/crates/tish_runtime/src/native_promise.rs +15 -0
  152. package/crates/tish_runtime/src/promise.rs +558 -0
  153. package/crates/tish_runtime/src/promise_io.rs +38 -0
  154. package/crates/tish_runtime/src/timers.rs +172 -0
  155. package/crates/tish_runtime/src/tty.rs +226 -0
  156. package/crates/tish_runtime/src/ws.rs +778 -0
  157. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  158. package/crates/tish_ui/Cargo.toml +17 -0
  159. package/crates/tish_ui/src/jsx.rs +692 -0
  160. package/crates/tish_ui/src/lib.rs +20 -0
  161. package/crates/tish_ui/src/runtime/hooks.rs +573 -0
  162. package/crates/tish_ui/src/runtime/mod.rs +183 -0
  163. package/crates/tish_vm/Cargo.toml +60 -0
  164. package/crates/tish_vm/src/jit.rs +1050 -0
  165. package/crates/tish_vm/src/lib.rs +41 -0
  166. package/crates/tish_vm/src/vm.rs +3536 -0
  167. package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
  168. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  169. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  170. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  171. package/crates/tish_wasm/Cargo.toml +15 -0
  172. package/crates/tish_wasm/src/lib.rs +428 -0
  173. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  174. package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
  175. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  176. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  177. package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
  178. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  179. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  180. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  181. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  182. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  183. package/justfile +276 -0
  184. package/package.json +2 -2
  185. package/platform/darwin-arm64/tish-fmt +0 -0
  186. package/platform/darwin-x64/tish-fmt +0 -0
  187. package/platform/linux-arm64/tish-fmt +0 -0
  188. package/platform/linux-x64/tish-fmt +0 -0
  189. package/platform/win32-x64/tish-fmt.exe +0 -0
@@ -0,0 +1,170 @@
1
+ //! Token types for the Tish lexer.
2
+
3
+ use std::sync::Arc;
4
+
5
+ #[derive(Debug, Clone, PartialEq)]
6
+ pub struct Span {
7
+ pub start: (usize, usize), // line, col
8
+ pub end: (usize, usize),
9
+ }
10
+
11
+ #[derive(Debug, Clone, PartialEq)]
12
+ pub struct Token {
13
+ pub kind: TokenKind,
14
+ pub span: Span,
15
+ pub literal: Option<Arc<str>>,
16
+ }
17
+
18
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19
+ pub enum TokenKind {
20
+ // Virtual tokens for optional braces
21
+ Indent,
22
+ Dedent,
23
+
24
+ // Literals
25
+ Number,
26
+ String,
27
+ True,
28
+ False,
29
+ Null,
30
+
31
+ // Identifiers and keywords
32
+ Ident,
33
+ Fn,
34
+ Let,
35
+ Const,
36
+ If,
37
+ Else,
38
+ While,
39
+ For,
40
+ Return,
41
+ Break,
42
+ Continue,
43
+ Throw,
44
+ Try,
45
+ Catch,
46
+ Finally,
47
+ Switch,
48
+ Case,
49
+ Default,
50
+ Do,
51
+ TypeOf,
52
+ Void,
53
+ Delete,
54
+ Of,
55
+ In,
56
+ Async,
57
+ Await,
58
+ New,
59
+ Import,
60
+ Export,
61
+ Type,
62
+ Declare,
63
+ Interface,
64
+ As,
65
+
66
+ // Punctuation
67
+ LParen,
68
+ RParen,
69
+ LBrace,
70
+ RBrace,
71
+ LBracket,
72
+ RBracket,
73
+ Semicolon,
74
+ Comma,
75
+ Dot,
76
+ Spread,
77
+ Colon,
78
+
79
+ // Operators
80
+ Assign,
81
+ AndAndAssign,
82
+ OrOrAssign,
83
+ NullishAssign,
84
+ PlusAssign,
85
+ MinusAssign,
86
+ StarAssign,
87
+ SlashAssign,
88
+ PercentAssign,
89
+ Eq,
90
+ Ne,
91
+ StrictEq,
92
+ StrictNe,
93
+ Lt,
94
+ Le,
95
+ Gt,
96
+ Ge,
97
+ Plus,
98
+ Minus,
99
+ PlusPlus,
100
+ MinusMinus,
101
+ Star,
102
+ StarStar,
103
+ Slash,
104
+ Percent,
105
+ And,
106
+ Or,
107
+ Not,
108
+ BitAnd,
109
+ BitOr,
110
+ BitXor,
111
+ BitNot,
112
+ Shl,
113
+ Shr,
114
+ UShr,
115
+ OptionalChain,
116
+ NullishCoalesce,
117
+ Question,
118
+ Arrow,
119
+
120
+ // Template literal tokens
121
+ TemplateNoSub, // `text` (no interpolation)
122
+ TemplateHead, // `text${ (start with interpolation)
123
+ TemplateMiddle, // }text${ (middle part)
124
+ TemplateTail, // }text` (end part)
125
+
126
+ JsxText, // Raw text in JSX children (emojis, etc.); only {}<> are special
127
+ }
128
+
129
+ impl TokenKind {
130
+ pub fn keyword_or_ident(s: &str) -> Self {
131
+ match s {
132
+ "fn" | "function" => TokenKind::Fn,
133
+ "let" => TokenKind::Let,
134
+ "const" => TokenKind::Const,
135
+ "if" => TokenKind::If,
136
+ "else" => TokenKind::Else,
137
+ "while" => TokenKind::While,
138
+ "for" => TokenKind::For,
139
+ "return" => TokenKind::Return,
140
+ "break" => TokenKind::Break,
141
+ "continue" => TokenKind::Continue,
142
+ "true" => TokenKind::True,
143
+ "false" => TokenKind::False,
144
+ "null" => TokenKind::Null,
145
+ "throw" => TokenKind::Throw,
146
+ "try" => TokenKind::Try,
147
+ "catch" => TokenKind::Catch,
148
+ "finally" => TokenKind::Finally,
149
+ "switch" => TokenKind::Switch,
150
+ "case" => TokenKind::Case,
151
+ "default" => TokenKind::Default,
152
+ "do" => TokenKind::Do,
153
+ "typeof" => TokenKind::TypeOf,
154
+ "void" => TokenKind::Void,
155
+ "delete" => TokenKind::Delete,
156
+ "of" => TokenKind::Of,
157
+ "in" => TokenKind::In,
158
+ "async" => TokenKind::Async,
159
+ "await" => TokenKind::Await,
160
+ "new" => TokenKind::New,
161
+ "import" => TokenKind::Import,
162
+ "export" => TokenKind::Export,
163
+ "type" => TokenKind::Type,
164
+ "declare" => TokenKind::Declare,
165
+ "interface" => TokenKind::Interface,
166
+ "as" => TokenKind::As,
167
+ _ => TokenKind::Ident,
168
+ }
169
+ }
170
+ }
@@ -0,0 +1,18 @@
1
+ [package]
2
+ name = "tishlang_lint"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "AST-based linter for Tish"
6
+ license-file = { workspace = true }
7
+ repository = { workspace = true }
8
+
9
+ [[bin]]
10
+ name = "tish-lint"
11
+ path = "src/bin/tish-lint.rs"
12
+
13
+ [dependencies]
14
+ clap = { version = "4.6.0", features = ["derive"] }
15
+ serde_json = "1.0"
16
+ walkdir = "2"
17
+ tishlang_ast = { path = "../tish_ast", version = ">=0.1" }
18
+ tishlang_parser = { path = "../tish_parser", version = ">=0.1" }
@@ -0,0 +1,195 @@
1
+ //! Standalone linter — not part of the `tish` compiler CLI.
2
+
3
+ use std::fs;
4
+ use std::path::{Path, PathBuf};
5
+
6
+ use clap::{Parser, ValueEnum};
7
+ use serde_json::json;
8
+
9
+ #[derive(Clone, Copy, Debug, ValueEnum)]
10
+ enum OutputFormat {
11
+ Text,
12
+ Sarif,
13
+ }
14
+
15
+ #[derive(Parser)]
16
+ #[command(name = "tish-lint")]
17
+ #[command(about = "AST-based linter for Tish")]
18
+ struct Cli {
19
+ /// Output format (SARIF 2.1.0 for code scanning integrations).
20
+ #[arg(long = "format", value_enum, default_value_t = OutputFormat::Text)]
21
+ output_format: OutputFormat,
22
+
23
+ #[arg(required = true)]
24
+ paths: Vec<String>,
25
+ }
26
+
27
+ #[derive(Debug)]
28
+ struct Issue {
29
+ path: PathBuf,
30
+ line: u32,
31
+ col: u32,
32
+ code: String,
33
+ message: String,
34
+ level: &'static str,
35
+ }
36
+
37
+ fn main() {
38
+ let cli = Cli::parse();
39
+ if let Err(e) = run(&cli.paths, cli.output_format) {
40
+ eprintln!("{}", e);
41
+ std::process::exit(1);
42
+ }
43
+ }
44
+
45
+ fn collect_files(paths: &[String]) -> Result<Vec<PathBuf>, String> {
46
+ let mut files: Vec<PathBuf> = Vec::new();
47
+ for p in paths {
48
+ let path = Path::new(p);
49
+ if path.is_dir() {
50
+ for e in walkdir::WalkDir::new(path)
51
+ .into_iter()
52
+ .filter_map(|e| e.ok())
53
+ {
54
+ if e.path().extension().map(|x| x == "tish").unwrap_or(false) {
55
+ files.push(e.path().to_path_buf());
56
+ }
57
+ }
58
+ } else if path.exists() {
59
+ files.push(path.to_path_buf());
60
+ } else {
61
+ return Err(format!("Not found: {}", p));
62
+ }
63
+ }
64
+ if files.is_empty() {
65
+ return Err("No .tish files to lint".into());
66
+ }
67
+ Ok(files)
68
+ }
69
+
70
+ fn run(paths: &[String], format: OutputFormat) -> Result<(), String> {
71
+ let files = collect_files(paths)?;
72
+ let mut issues: Vec<Issue> = Vec::new();
73
+ for f in files {
74
+ let src = fs::read_to_string(&f).map_err(|e| format!("{}: {}", f.display(), e))?;
75
+ match tishlang_lint::lint_source(&src) {
76
+ Ok(diags) => {
77
+ for d in diags {
78
+ let level = match d.severity {
79
+ tishlang_lint::Severity::Error => "error",
80
+ tishlang_lint::Severity::Warning => "warning",
81
+ };
82
+ issues.push(Issue {
83
+ path: f.clone(),
84
+ line: d.line,
85
+ col: d.col,
86
+ code: d.code.to_string(),
87
+ message: d.message,
88
+ level,
89
+ });
90
+ }
91
+ }
92
+ Err(e) => {
93
+ issues.push(Issue {
94
+ path: f.clone(),
95
+ line: 1,
96
+ col: 1,
97
+ code: "tish-parse-error".into(),
98
+ message: e,
99
+ level: "error",
100
+ });
101
+ }
102
+ }
103
+ }
104
+
105
+ let error_count = issues.iter().filter(|i| i.level == "error").count();
106
+
107
+ match format {
108
+ OutputFormat::Text => {
109
+ for i in &issues {
110
+ println!(
111
+ "{}:{}:{}: {} [{}] {}",
112
+ i.path.display(),
113
+ i.line,
114
+ i.col,
115
+ i.level,
116
+ i.code,
117
+ i.message
118
+ );
119
+ }
120
+ if error_count > 0 {
121
+ return Err(format!("{} issue(s)", error_count));
122
+ }
123
+ }
124
+ OutputFormat::Sarif => {
125
+ print_sarif(&issues)?;
126
+ if error_count > 0 {
127
+ return Err(format!("{} issue(s)", error_count));
128
+ }
129
+ }
130
+ }
131
+
132
+ Ok(())
133
+ }
134
+
135
+ fn print_sarif(issues: &[Issue]) -> Result<(), String> {
136
+ let rules: Vec<_> = tishlang_lint::RULES
137
+ .iter()
138
+ .map(|(id, desc)| {
139
+ json!({
140
+ "id": id,
141
+ "name": id,
142
+ "shortDescription": { "text": desc },
143
+ "helpUri": "https://tishlang.com/docs/reference/linting/"
144
+ })
145
+ })
146
+ .chain(std::iter::once(json!({
147
+ "id": "tish-parse-error",
148
+ "name": "tish-parse-error",
149
+ "shortDescription": { "text": "Source failed to parse as Tish." },
150
+ "helpUri": "https://tishlang.com/docs/language/overview/"
151
+ })))
152
+ .collect();
153
+
154
+ let results: Vec<_> = issues
155
+ .iter()
156
+ .map(|i| {
157
+ let uri = i.path.to_str().unwrap_or("unknown").replace('\\', "/");
158
+ json!({
159
+ "ruleId": i.code,
160
+ "level": i.level,
161
+ "message": { "text": i.message },
162
+ "locations": [{
163
+ "physicalLocation": {
164
+ "artifactLocation": { "uri": uri },
165
+ "region": {
166
+ "startLine": i.line,
167
+ "startColumn": i.col
168
+ }
169
+ }
170
+ }]
171
+ })
172
+ })
173
+ .collect();
174
+
175
+ let doc = json!({
176
+ "version": "2.1.0",
177
+ "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
178
+ "runs": [{
179
+ "tool": {
180
+ "driver": {
181
+ "name": "tish-lint",
182
+ "informationUri": "https://tishlang.com/docs/reference/linting/",
183
+ "rules": rules
184
+ }
185
+ },
186
+ "results": results
187
+ }]
188
+ });
189
+
190
+ println!(
191
+ "{}",
192
+ serde_json::to_string_pretty(&doc).map_err(|e| e.to_string())?
193
+ );
194
+ Ok(())
195
+ }
@@ -0,0 +1,281 @@
1
+ //! AST-based lints for Tish. Rule IDs are stable for CI and editors.
2
+
3
+ use std::collections::HashSet;
4
+
5
+ use tishlang_ast::{Expr, ObjectProp, Program, Statement};
6
+
7
+ #[derive(Debug, Clone, PartialEq, Eq)]
8
+ pub enum Severity {
9
+ Warning,
10
+ Error,
11
+ }
12
+
13
+ #[derive(Debug, Clone)]
14
+ pub struct LintDiagnostic {
15
+ pub code: &'static str,
16
+ pub message: String,
17
+ /// 1-based line
18
+ pub line: u32,
19
+ /// 1-based column (UTF-16 offset preferred for LSP; we use byte col as approx)
20
+ pub col: u32,
21
+ pub severity: Severity,
22
+ }
23
+
24
+ /// Run all default rules on parsed program.
25
+ pub fn lint_program(program: &Program) -> Vec<LintDiagnostic> {
26
+ let mut out = Vec::new();
27
+ for s in &program.statements {
28
+ lint_stmt(s, &mut out);
29
+ }
30
+ out
31
+ }
32
+
33
+ /// Lint source: parse then lint. Parse errors are not reported here.
34
+ pub fn lint_source(source: &str) -> Result<Vec<LintDiagnostic>, String> {
35
+ let program = tishlang_parser::parse(source)?;
36
+ Ok(lint_program(&program))
37
+ }
38
+
39
+ fn lint_stmt(s: &Statement, out: &mut Vec<LintDiagnostic>) {
40
+ match s {
41
+ Statement::Try {
42
+ body,
43
+ catch_param,
44
+ catch_body,
45
+ finally_body,
46
+ ..
47
+ } => {
48
+ lint_stmt(body, out);
49
+ if let (Some(_), Some(cb)) = (catch_param, catch_body) {
50
+ if is_empty_block_or_stmt(cb) {
51
+ let span = stmt_span(cb);
52
+ out.push(LintDiagnostic {
53
+ code: "tish-empty-catch",
54
+ message: "Empty catch block; handle or rethrow the error.".into(),
55
+ line: span.0,
56
+ col: span.1,
57
+ severity: Severity::Warning,
58
+ });
59
+ }
60
+ lint_stmt(cb, out);
61
+ }
62
+ if let Some(fb) = finally_body {
63
+ lint_stmt(fb, out);
64
+ }
65
+ }
66
+ Statement::Block { statements, .. } => {
67
+ for st in statements {
68
+ lint_stmt(st, out);
69
+ }
70
+ }
71
+ Statement::If {
72
+ then_branch,
73
+ else_branch,
74
+ ..
75
+ } => {
76
+ lint_stmt(then_branch, out);
77
+ if let Some(e) = else_branch {
78
+ lint_stmt(e, out);
79
+ }
80
+ }
81
+ Statement::While { body, .. } | Statement::ForOf { body, .. } => lint_stmt(body, out),
82
+ Statement::For { init, body, .. } => {
83
+ if let Some(i) = init {
84
+ lint_stmt(i, out);
85
+ }
86
+ lint_stmt(body, out);
87
+ }
88
+ Statement::FunDecl { body, .. } => lint_stmt(body, out),
89
+ Statement::Switch {
90
+ cases,
91
+ default_body,
92
+ ..
93
+ } => {
94
+ for (_, stmts) in cases {
95
+ for st in stmts {
96
+ lint_stmt(st, out);
97
+ }
98
+ }
99
+ if let Some(def) = default_body {
100
+ for st in def {
101
+ lint_stmt(st, out);
102
+ }
103
+ }
104
+ }
105
+ Statement::DoWhile { body, .. } => lint_stmt(body, out),
106
+ Statement::Export { declaration, .. } => {
107
+ if let tishlang_ast::ExportDeclaration::Named(inner) = declaration.as_ref() {
108
+ lint_stmt(inner, out);
109
+ }
110
+ }
111
+ Statement::ExprStmt { expr, .. } => lint_expr(expr, out),
112
+ Statement::VarDecl { init: Some(e), .. } => lint_expr(e, out),
113
+ Statement::VarDeclDestructure { init, .. } => lint_expr(init, out),
114
+ Statement::Return { value: Some(e), .. } => lint_expr(e, out),
115
+ Statement::Throw { value, .. } => lint_expr(value, out),
116
+ _ => {}
117
+ }
118
+ }
119
+
120
+ fn is_empty_block_or_stmt(s: &Statement) -> bool {
121
+ match s {
122
+ Statement::Block { statements, .. } => statements.is_empty(),
123
+ Statement::ExprStmt { .. } => false,
124
+ _ => false,
125
+ }
126
+ }
127
+
128
+ fn stmt_span(s: &Statement) -> (u32, u32) {
129
+ let sp = match s {
130
+ Statement::Block { span, .. } => *span,
131
+ Statement::Try { span, .. } => *span,
132
+ _ => tishlang_ast::Span {
133
+ start: (1, 1),
134
+ end: (1, 1),
135
+ },
136
+ };
137
+ (sp.start.0 as u32, sp.start.1 as u32)
138
+ }
139
+
140
+ fn lint_expr(e: &Expr, out: &mut Vec<LintDiagnostic>) {
141
+ match e {
142
+ Expr::Object { props, span, .. } => {
143
+ let mut seen: HashSet<&str> = HashSet::new();
144
+ for p in props {
145
+ if let ObjectProp::KeyValue(k, v) = p {
146
+ if !seen.insert(k.as_ref()) {
147
+ out.push(LintDiagnostic {
148
+ code: "tish-duplicate-key",
149
+ message: format!("Duplicate object key `{}`", k),
150
+ line: span.start.0 as u32,
151
+ col: span.start.1 as u32,
152
+ severity: Severity::Warning,
153
+ });
154
+ }
155
+ lint_expr(v, out);
156
+ } else if let ObjectProp::Spread(ex) = p {
157
+ lint_expr(ex, out);
158
+ }
159
+ }
160
+ }
161
+ Expr::Binary { left, right, .. } => {
162
+ lint_expr(left, out);
163
+ lint_expr(right, out);
164
+ }
165
+ Expr::Unary { operand, .. } => lint_expr(operand, out),
166
+ Expr::Call { callee, args, .. } => {
167
+ lint_expr(callee, out);
168
+ for a in args {
169
+ match a {
170
+ tishlang_ast::CallArg::Expr(x) => lint_expr(x, out),
171
+ tishlang_ast::CallArg::Spread(x) => lint_expr(x, out),
172
+ }
173
+ }
174
+ }
175
+ Expr::New { callee, args, .. } => {
176
+ lint_expr(callee, out);
177
+ for a in args {
178
+ match a {
179
+ tishlang_ast::CallArg::Expr(x) => lint_expr(x, out),
180
+ tishlang_ast::CallArg::Spread(x) => lint_expr(x, out),
181
+ }
182
+ }
183
+ }
184
+ Expr::Member { object, .. } => {
185
+ lint_expr(object, out);
186
+ }
187
+ Expr::Index { object, index, .. } => {
188
+ lint_expr(object, out);
189
+ lint_expr(index, out);
190
+ }
191
+ Expr::Conditional {
192
+ cond,
193
+ then_branch,
194
+ else_branch,
195
+ ..
196
+ } => {
197
+ lint_expr(cond, out);
198
+ lint_expr(then_branch, out);
199
+ lint_expr(else_branch, out);
200
+ }
201
+ Expr::NullishCoalesce { left, right, .. } => {
202
+ lint_expr(left, out);
203
+ lint_expr(right, out);
204
+ }
205
+ Expr::Array { elements, .. } => {
206
+ for el in elements {
207
+ match el {
208
+ tishlang_ast::ArrayElement::Expr(x) => lint_expr(x, out),
209
+ tishlang_ast::ArrayElement::Spread(x) => lint_expr(x, out),
210
+ }
211
+ }
212
+ }
213
+ Expr::Assign { value, .. }
214
+ | Expr::CompoundAssign { value, .. }
215
+ | Expr::LogicalAssign { value, .. } => lint_expr(value, out),
216
+ Expr::MemberAssign { object, value, .. } => {
217
+ lint_expr(object, out);
218
+ lint_expr(value, out);
219
+ }
220
+ Expr::IndexAssign {
221
+ object,
222
+ index,
223
+ value,
224
+ ..
225
+ } => {
226
+ lint_expr(object, out);
227
+ lint_expr(index, out);
228
+ lint_expr(value, out);
229
+ }
230
+ Expr::ArrowFunction { body, .. } => match body {
231
+ tishlang_ast::ArrowBody::Expr(x) => lint_expr(x, out),
232
+ tishlang_ast::ArrowBody::Block(b) => lint_stmt(b, out),
233
+ },
234
+ Expr::TemplateLiteral { exprs, .. } => {
235
+ for x in exprs {
236
+ lint_expr(x, out);
237
+ }
238
+ }
239
+ Expr::Await { operand, .. } => lint_expr(operand, out),
240
+ Expr::TypeOf { operand, .. } => lint_expr(operand, out),
241
+ Expr::JsxElement {
242
+ props, children, ..
243
+ } => {
244
+ for pr in props {
245
+ match pr {
246
+ tishlang_ast::JsxProp::Attr { value, .. } => {
247
+ if let tishlang_ast::JsxAttrValue::Expr(x) = value {
248
+ lint_expr(x, out);
249
+ }
250
+ }
251
+ tishlang_ast::JsxProp::Spread(x) => lint_expr(x, out),
252
+ }
253
+ }
254
+ for ch in children {
255
+ if let tishlang_ast::JsxChild::Expr(x) = ch {
256
+ lint_expr(x, out);
257
+ }
258
+ }
259
+ }
260
+ Expr::JsxFragment { children, .. } => {
261
+ for ch in children {
262
+ if let tishlang_ast::JsxChild::Expr(x) = ch {
263
+ lint_expr(x, out);
264
+ }
265
+ }
266
+ }
267
+ _ => {}
268
+ }
269
+ }
270
+
271
+ /// Human-readable catalog for documentation.
272
+ pub const RULES: &[(&str, &str)] = &[
273
+ (
274
+ "tish-empty-catch",
275
+ "Warns on catch blocks with no statements (likely mistake).",
276
+ ),
277
+ (
278
+ "tish-duplicate-key",
279
+ "Warns when an object literal repeats the same key.",
280
+ ),
281
+ ];
@@ -0,0 +1,13 @@
1
+ [package]
2
+ name = "tishlang_llvm"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "LLVM backend for Tish (bytecode → native via clang/LLVM)"
6
+
7
+ license-file = { workspace = true }
8
+ repository = { workspace = true }
9
+ [dependencies]
10
+ tishlang_bytecode = { path = "../tish_bytecode", version = ">=0.1" }
11
+ tishlang_compile = { path = "../tish_compile", version = ">=0.1" }
12
+ tishlang_cranelift = { path = "../tish_cranelift", version = ">=0.1" }
13
+ tishlang_build_utils = { path = "../tish_build_utils", version = ">=0.1" }