@tishlang/tish-format 1.0.12 → 1.0.13

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 (164) hide show
  1. package/Cargo.toml +49 -0
  2. package/LICENSE +13 -0
  3. package/README.md +138 -0
  4. package/bin/tish-format +0 -0
  5. package/crates/js_to_tish/Cargo.toml +11 -0
  6. package/crates/js_to_tish/README.md +18 -0
  7. package/crates/js_to_tish/src/error.rs +55 -0
  8. package/crates/js_to_tish/src/lib.rs +11 -0
  9. package/crates/js_to_tish/src/span_util.rs +35 -0
  10. package/crates/js_to_tish/src/transform/expr.rs +610 -0
  11. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  12. package/crates/js_to_tish/src/transform.rs +60 -0
  13. package/crates/tish/Cargo.toml +54 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +565 -0
  16. package/crates/tish/src/main.rs +781 -0
  17. package/crates/tish/src/repl_completion.rs +200 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  24. package/crates/tish/tests/integration_test.rs +1095 -0
  25. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  26. package/crates/tish/tests/shortcircuit.rs +65 -0
  27. package/crates/tish_ast/Cargo.toml +9 -0
  28. package/crates/tish_ast/src/ast.rs +620 -0
  29. package/crates/tish_ast/src/lib.rs +5 -0
  30. package/crates/tish_build_utils/Cargo.toml +11 -0
  31. package/crates/tish_build_utils/src/lib.rs +577 -0
  32. package/crates/tish_builtins/Cargo.toml +20 -0
  33. package/crates/tish_builtins/src/array.rs +441 -0
  34. package/crates/tish_builtins/src/construct.rs +159 -0
  35. package/crates/tish_builtins/src/globals.rs +213 -0
  36. package/crates/tish_builtins/src/helpers.rs +35 -0
  37. package/crates/tish_builtins/src/lib.rs +16 -0
  38. package/crates/tish_builtins/src/math.rs +89 -0
  39. package/crates/tish_builtins/src/object.rs +36 -0
  40. package/crates/tish_builtins/src/string.rs +647 -0
  41. package/crates/tish_builtins/src/symbol.rs +83 -0
  42. package/crates/tish_bytecode/Cargo.toml +17 -0
  43. package/crates/tish_bytecode/src/chunk.rs +96 -0
  44. package/crates/tish_bytecode/src/compiler.rs +1760 -0
  45. package/crates/tish_bytecode/src/encoding.rs +100 -0
  46. package/crates/tish_bytecode/src/lib.rs +19 -0
  47. package/crates/tish_bytecode/src/opcode.rs +142 -0
  48. package/crates/tish_bytecode/src/peephole.rs +189 -0
  49. package/crates/tish_bytecode/src/serialize.rs +163 -0
  50. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  51. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  52. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  53. package/crates/tish_compile/Cargo.toml +26 -0
  54. package/crates/tish_compile/src/codegen.rs +5332 -0
  55. package/crates/tish_compile/src/infer.rs +292 -0
  56. package/crates/tish_compile/src/lib.rs +164 -0
  57. package/crates/tish_compile/src/resolve.rs +1388 -0
  58. package/crates/tish_compile/src/types.rs +501 -0
  59. package/crates/tish_compile_js/Cargo.toml +18 -0
  60. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  61. package/crates/tish_compile_js/src/codegen.rs +871 -0
  62. package/crates/tish_compile_js/src/error.rs +20 -0
  63. package/crates/tish_compile_js/src/lib.rs +26 -0
  64. package/crates/tish_compile_js/src/tests_jsx.rs +350 -0
  65. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  66. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  67. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  68. package/crates/tish_core/Cargo.toml +26 -0
  69. package/crates/tish_core/src/console_style.rs +160 -0
  70. package/crates/tish_core/src/json.rs +387 -0
  71. package/crates/tish_core/src/lib.rs +17 -0
  72. package/crates/tish_core/src/macros.rs +36 -0
  73. package/crates/tish_core/src/uri.rs +118 -0
  74. package/crates/tish_core/src/value.rs +696 -0
  75. package/crates/tish_core/src/vmref.rs +178 -0
  76. package/crates/tish_cranelift/Cargo.toml +19 -0
  77. package/crates/tish_cranelift/src/lib.rs +43 -0
  78. package/crates/tish_cranelift/src/link.rs +117 -0
  79. package/crates/tish_cranelift/src/lower.rs +85 -0
  80. package/crates/tish_cranelift_runtime/Cargo.toml +25 -0
  81. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  82. package/crates/tish_eval/Cargo.toml +45 -0
  83. package/crates/tish_eval/src/eval.rs +3717 -0
  84. package/crates/tish_eval/src/http.rs +188 -0
  85. package/crates/tish_eval/src/lib.rs +99 -0
  86. package/crates/tish_eval/src/natives.rs +399 -0
  87. package/crates/tish_eval/src/promise.rs +179 -0
  88. package/crates/tish_eval/src/regex.rs +299 -0
  89. package/crates/tish_eval/src/timers.rs +120 -0
  90. package/crates/tish_eval/src/value.rs +318 -0
  91. package/crates/tish_eval/src/value_convert.rs +111 -0
  92. package/crates/tish_fmt/Cargo.toml +16 -0
  93. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  94. package/crates/tish_fmt/src/lib.rs +2101 -0
  95. package/crates/tish_jsx_web/Cargo.toml +9 -0
  96. package/crates/tish_jsx_web/README.md +5 -0
  97. package/crates/tish_jsx_web/src/lib.rs +2 -0
  98. package/crates/tish_lexer/Cargo.toml +9 -0
  99. package/crates/tish_lexer/src/lib.rs +716 -0
  100. package/crates/tish_lexer/src/token.rs +163 -0
  101. package/crates/tish_lint/Cargo.toml +18 -0
  102. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  103. package/crates/tish_lint/src/lib.rs +289 -0
  104. package/crates/tish_llvm/Cargo.toml +13 -0
  105. package/crates/tish_llvm/src/lib.rs +115 -0
  106. package/crates/tish_lsp/Cargo.toml +25 -0
  107. package/crates/tish_lsp/README.md +26 -0
  108. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  109. package/crates/tish_lsp/src/import_goto.rs +562 -0
  110. package/crates/tish_lsp/src/main.rs +1046 -0
  111. package/crates/tish_native/Cargo.toml +16 -0
  112. package/crates/tish_native/src/build.rs +427 -0
  113. package/crates/tish_native/src/config.rs +48 -0
  114. package/crates/tish_native/src/lib.rs +416 -0
  115. package/crates/tish_opt/Cargo.toml +13 -0
  116. package/crates/tish_opt/src/lib.rs +943 -0
  117. package/crates/tish_parser/Cargo.toml +11 -0
  118. package/crates/tish_parser/src/lib.rs +332 -0
  119. package/crates/tish_parser/src/parser.rs +2304 -0
  120. package/crates/tish_pg/Cargo.toml +34 -0
  121. package/crates/tish_pg/README.md +38 -0
  122. package/crates/tish_pg/src/error.rs +52 -0
  123. package/crates/tish_pg/src/lib.rs +955 -0
  124. package/crates/tish_resolve/Cargo.toml +13 -0
  125. package/crates/tish_resolve/src/lib.rs +3561 -0
  126. package/crates/tish_resolve/src/pos.rs +141 -0
  127. package/crates/tish_runtime/Cargo.toml +96 -0
  128. package/crates/tish_runtime/src/http.rs +1298 -0
  129. package/crates/tish_runtime/src/http_fetch.rs +471 -0
  130. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  131. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  132. package/crates/tish_runtime/src/lib.rs +1192 -0
  133. package/crates/tish_runtime/src/native_promise.rs +15 -0
  134. package/crates/tish_runtime/src/promise.rs +248 -0
  135. package/crates/tish_runtime/src/promise_io.rs +38 -0
  136. package/crates/tish_runtime/src/timers.rs +166 -0
  137. package/crates/tish_runtime/src/ws.rs +761 -0
  138. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  139. package/crates/tish_ui/Cargo.toml +17 -0
  140. package/crates/tish_ui/src/jsx.rs +682 -0
  141. package/crates/tish_ui/src/lib.rs +20 -0
  142. package/crates/tish_ui/src/runtime/hooks.rs +569 -0
  143. package/crates/tish_ui/src/runtime/mod.rs +180 -0
  144. package/crates/tish_vm/Cargo.toml +47 -0
  145. package/crates/tish_vm/src/lib.rs +39 -0
  146. package/crates/tish_vm/src/vm.rs +2192 -0
  147. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  148. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  149. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  150. package/crates/tish_wasm/Cargo.toml +15 -0
  151. package/crates/tish_wasm/src/lib.rs +424 -0
  152. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  153. package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
  154. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  155. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  156. package/crates/tishlang_cargo_bindgen/src/classify.rs +263 -0
  157. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  158. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  159. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  160. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  161. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  162. package/justfile +268 -0
  163. package/package.json +1 -1
  164. package/platform/darwin-arm64/tish-fmt +0 -0
@@ -0,0 +1,163 @@
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
+ Of,
54
+ In,
55
+ Async,
56
+ Await,
57
+ New,
58
+ Import,
59
+ Export,
60
+ Type,
61
+ Declare,
62
+
63
+ // Punctuation
64
+ LParen,
65
+ RParen,
66
+ LBrace,
67
+ RBrace,
68
+ LBracket,
69
+ RBracket,
70
+ Semicolon,
71
+ Comma,
72
+ Dot,
73
+ Spread,
74
+ Colon,
75
+
76
+ // Operators
77
+ Assign,
78
+ AndAndAssign,
79
+ OrOrAssign,
80
+ NullishAssign,
81
+ PlusAssign,
82
+ MinusAssign,
83
+ StarAssign,
84
+ SlashAssign,
85
+ PercentAssign,
86
+ Eq,
87
+ Ne,
88
+ StrictEq,
89
+ StrictNe,
90
+ Lt,
91
+ Le,
92
+ Gt,
93
+ Ge,
94
+ Plus,
95
+ Minus,
96
+ PlusPlus,
97
+ MinusMinus,
98
+ Star,
99
+ StarStar,
100
+ Slash,
101
+ Percent,
102
+ And,
103
+ Or,
104
+ Not,
105
+ BitAnd,
106
+ BitOr,
107
+ BitXor,
108
+ BitNot,
109
+ Shl,
110
+ Shr,
111
+ OptionalChain,
112
+ NullishCoalesce,
113
+ Question,
114
+ Arrow,
115
+
116
+ // Template literal tokens
117
+ TemplateNoSub, // `text` (no interpolation)
118
+ TemplateHead, // `text${ (start with interpolation)
119
+ TemplateMiddle, // }text${ (middle part)
120
+ TemplateTail, // }text` (end part)
121
+
122
+ JsxText, // Raw text in JSX children (emojis, etc.); only {}<> are special
123
+ }
124
+
125
+ impl TokenKind {
126
+ pub fn keyword_or_ident(s: &str) -> Self {
127
+ match s {
128
+ "fn" | "function" => TokenKind::Fn,
129
+ "let" => TokenKind::Let,
130
+ "const" => TokenKind::Const,
131
+ "if" => TokenKind::If,
132
+ "else" => TokenKind::Else,
133
+ "while" => TokenKind::While,
134
+ "for" => TokenKind::For,
135
+ "return" => TokenKind::Return,
136
+ "break" => TokenKind::Break,
137
+ "continue" => TokenKind::Continue,
138
+ "true" => TokenKind::True,
139
+ "false" => TokenKind::False,
140
+ "null" => TokenKind::Null,
141
+ "throw" => TokenKind::Throw,
142
+ "try" => TokenKind::Try,
143
+ "catch" => TokenKind::Catch,
144
+ "finally" => TokenKind::Finally,
145
+ "switch" => TokenKind::Switch,
146
+ "case" => TokenKind::Case,
147
+ "default" => TokenKind::Default,
148
+ "do" => TokenKind::Do,
149
+ "typeof" => TokenKind::TypeOf,
150
+ "void" => TokenKind::Void,
151
+ "of" => TokenKind::Of,
152
+ "in" => TokenKind::In,
153
+ "async" => TokenKind::Async,
154
+ "await" => TokenKind::Await,
155
+ "new" => TokenKind::New,
156
+ "import" => TokenKind::Import,
157
+ "export" => TokenKind::Export,
158
+ "type" => TokenKind::Type,
159
+ "declare" => TokenKind::Declare,
160
+ _ => TokenKind::Ident,
161
+ }
162
+ }
163
+ }
@@ -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,289 @@
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, .. } => {
113
+ if let Some(e) = init {
114
+ lint_expr(e, out);
115
+ }
116
+ }
117
+ Statement::VarDeclDestructure { init, .. } => lint_expr(init, out),
118
+ Statement::Return { value, .. } => {
119
+ if let Some(e) = value {
120
+ lint_expr(e, out);
121
+ }
122
+ }
123
+ Statement::Throw { value, .. } => lint_expr(value, out),
124
+ _ => {}
125
+ }
126
+ }
127
+
128
+ fn is_empty_block_or_stmt(s: &Statement) -> bool {
129
+ match s {
130
+ Statement::Block { statements, .. } => statements.is_empty(),
131
+ Statement::ExprStmt { .. } => false,
132
+ _ => false,
133
+ }
134
+ }
135
+
136
+ fn stmt_span(s: &Statement) -> (u32, u32) {
137
+ let sp = match s {
138
+ Statement::Block { span, .. } => *span,
139
+ Statement::Try { span, .. } => *span,
140
+ _ => tishlang_ast::Span {
141
+ start: (1, 1),
142
+ end: (1, 1),
143
+ },
144
+ };
145
+ (sp.start.0 as u32, sp.start.1 as u32)
146
+ }
147
+
148
+ fn lint_expr(e: &Expr, out: &mut Vec<LintDiagnostic>) {
149
+ match e {
150
+ Expr::Object { props, span, .. } => {
151
+ let mut seen: HashSet<&str> = HashSet::new();
152
+ for p in props {
153
+ if let ObjectProp::KeyValue(k, v) = p {
154
+ if !seen.insert(k.as_ref()) {
155
+ out.push(LintDiagnostic {
156
+ code: "tish-duplicate-key",
157
+ message: format!("Duplicate object key `{}`", k),
158
+ line: span.start.0 as u32,
159
+ col: span.start.1 as u32,
160
+ severity: Severity::Warning,
161
+ });
162
+ }
163
+ lint_expr(v, out);
164
+ } else if let ObjectProp::Spread(ex) = p {
165
+ lint_expr(ex, out);
166
+ }
167
+ }
168
+ }
169
+ Expr::Binary { left, right, .. } => {
170
+ lint_expr(left, out);
171
+ lint_expr(right, out);
172
+ }
173
+ Expr::Unary { operand, .. } => lint_expr(operand, out),
174
+ Expr::Call { callee, args, .. } => {
175
+ lint_expr(callee, out);
176
+ for a in args {
177
+ match a {
178
+ tishlang_ast::CallArg::Expr(x) => lint_expr(x, out),
179
+ tishlang_ast::CallArg::Spread(x) => lint_expr(x, out),
180
+ }
181
+ }
182
+ }
183
+ Expr::New { callee, args, .. } => {
184
+ lint_expr(callee, out);
185
+ for a in args {
186
+ match a {
187
+ tishlang_ast::CallArg::Expr(x) => lint_expr(x, out),
188
+ tishlang_ast::CallArg::Spread(x) => lint_expr(x, out),
189
+ }
190
+ }
191
+ }
192
+ Expr::Member { object, .. } => {
193
+ lint_expr(object, out);
194
+ }
195
+ Expr::Index { object, index, .. } => {
196
+ lint_expr(object, out);
197
+ lint_expr(index, out);
198
+ }
199
+ Expr::Conditional {
200
+ cond,
201
+ then_branch,
202
+ else_branch,
203
+ ..
204
+ } => {
205
+ lint_expr(cond, out);
206
+ lint_expr(then_branch, out);
207
+ lint_expr(else_branch, out);
208
+ }
209
+ Expr::NullishCoalesce { left, right, .. } => {
210
+ lint_expr(left, out);
211
+ lint_expr(right, out);
212
+ }
213
+ Expr::Array { elements, .. } => {
214
+ for el in elements {
215
+ match el {
216
+ tishlang_ast::ArrayElement::Expr(x) => lint_expr(x, out),
217
+ tishlang_ast::ArrayElement::Spread(x) => lint_expr(x, out),
218
+ }
219
+ }
220
+ }
221
+ Expr::Assign { value, .. }
222
+ | Expr::CompoundAssign { value, .. }
223
+ | Expr::LogicalAssign { value, .. } => lint_expr(value, out),
224
+ Expr::MemberAssign { object, value, .. } => {
225
+ lint_expr(object, out);
226
+ lint_expr(value, out);
227
+ }
228
+ Expr::IndexAssign {
229
+ object,
230
+ index,
231
+ value,
232
+ ..
233
+ } => {
234
+ lint_expr(object, out);
235
+ lint_expr(index, out);
236
+ lint_expr(value, out);
237
+ }
238
+ Expr::ArrowFunction { body, .. } => match body {
239
+ tishlang_ast::ArrowBody::Expr(x) => lint_expr(x, out),
240
+ tishlang_ast::ArrowBody::Block(b) => lint_stmt(b, out),
241
+ },
242
+ Expr::TemplateLiteral { exprs, .. } => {
243
+ for x in exprs {
244
+ lint_expr(x, out);
245
+ }
246
+ }
247
+ Expr::Await { operand, .. } => lint_expr(operand, out),
248
+ Expr::TypeOf { operand, .. } => lint_expr(operand, out),
249
+ Expr::JsxElement {
250
+ props, children, ..
251
+ } => {
252
+ for pr in props {
253
+ match pr {
254
+ tishlang_ast::JsxProp::Attr { value, .. } => {
255
+ if let tishlang_ast::JsxAttrValue::Expr(x) = value {
256
+ lint_expr(x, out);
257
+ }
258
+ }
259
+ tishlang_ast::JsxProp::Spread(x) => lint_expr(x, out),
260
+ }
261
+ }
262
+ for ch in children {
263
+ if let tishlang_ast::JsxChild::Expr(x) = ch {
264
+ lint_expr(x, out);
265
+ }
266
+ }
267
+ }
268
+ Expr::JsxFragment { children, .. } => {
269
+ for ch in children {
270
+ if let tishlang_ast::JsxChild::Expr(x) = ch {
271
+ lint_expr(x, out);
272
+ }
273
+ }
274
+ }
275
+ _ => {}
276
+ }
277
+ }
278
+
279
+ /// Human-readable catalog for documentation.
280
+ pub const RULES: &[(&str, &str)] = &[
281
+ (
282
+ "tish-empty-catch",
283
+ "Warns on catch blocks with no statements (likely mistake).",
284
+ ),
285
+ (
286
+ "tish-duplicate-key",
287
+ "Warns when an object literal repeats the same key.",
288
+ ),
289
+ ];
@@ -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" }