@tishlang/tish 1.3.8 → 1.4.2

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.
package/bin/tish CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tishlang"
3
- version = "1.3.8"
3
+ version = "1.4.2"
4
4
  edition = "2021"
5
5
  description = "Tish CLI - run, REPL, compile to native"
6
6
  license-file = { workspace = true }
@@ -12,6 +12,7 @@ path = "src/bin/tish-lint.rs"
12
12
 
13
13
  [dependencies]
14
14
  clap = { version = "4.6.0", features = ["derive"] }
15
+ serde_json = "1.0"
15
16
  walkdir = "2"
16
17
  tishlang_ast = { path = "../tish_ast", version = ">=0.1" }
17
18
  tishlang_parser = { path = "../tish_parser", version = ">=0.1" }
@@ -3,30 +3,54 @@
3
3
  use std::fs;
4
4
  use std::path::{Path, PathBuf};
5
5
 
6
- use clap::Parser;
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
+ }
7
14
 
8
15
  #[derive(Parser)]
9
16
  #[command(name = "tish-lint")]
10
17
  #[command(about = "AST-based linter for Tish")]
11
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
+
12
23
  #[arg(required = true)]
13
24
  paths: Vec<String>,
14
25
  }
15
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
+
16
37
  fn main() {
17
38
  let cli = Cli::parse();
18
- if let Err(e) = run(&cli.paths) {
39
+ if let Err(e) = run(&cli.paths, cli.output_format) {
19
40
  eprintln!("{}", e);
20
41
  std::process::exit(1);
21
42
  }
22
43
  }
23
44
 
24
- fn run(paths: &[String]) -> Result<(), String> {
45
+ fn collect_files(paths: &[String]) -> Result<Vec<PathBuf>, String> {
25
46
  let mut files: Vec<PathBuf> = Vec::new();
26
47
  for p in paths {
27
48
  let path = Path::new(p);
28
49
  if path.is_dir() {
29
- for e in walkdir::WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
50
+ for e in walkdir::WalkDir::new(path)
51
+ .into_iter()
52
+ .filter_map(|e| e.ok())
53
+ {
30
54
  if e.path().extension().map(|x| x == "tish").unwrap_or(false) {
31
55
  files.push(e.path().to_path_buf());
32
56
  }
@@ -40,38 +64,132 @@ fn run(paths: &[String]) -> Result<(), String> {
40
64
  if files.is_empty() {
41
65
  return Err("No .tish files to lint".into());
42
66
  }
43
- let mut errors = 0;
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();
44
73
  for f in files {
45
74
  let src = fs::read_to_string(&f).map_err(|e| format!("{}: {}", f.display(), e))?;
46
75
  match tishlang_lint::lint_source(&src) {
47
76
  Ok(diags) => {
48
77
  for d in diags {
49
- let sev = match d.severity {
50
- tishlang_lint::Severity::Error => {
51
- errors += 1;
52
- "error"
53
- }
78
+ let level = match d.severity {
79
+ tishlang_lint::Severity::Error => "error",
54
80
  tishlang_lint::Severity::Warning => "warning",
55
81
  };
56
- println!(
57
- "{}:{}:{}: {} [{}] {}",
58
- f.display(),
59
- d.line,
60
- d.col,
61
- sev,
62
- d.code,
63
- d.message
64
- );
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
+ });
65
90
  }
66
91
  }
67
92
  Err(e) => {
68
- eprintln!("{}: parse error: {}", f.display(), e);
69
- errors += 1;
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
+ });
70
101
  }
71
102
  }
72
103
  }
73
- if errors > 0 {
74
- return Err(format!("{} issue(s)", errors));
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
+ }
75
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
+ );
76
194
  Ok(())
77
195
  }
@@ -246,7 +246,9 @@ fn lint_expr(e: &Expr, out: &mut Vec<LintDiagnostic>) {
246
246
  }
247
247
  Expr::Await { operand, .. } => lint_expr(operand, out),
248
248
  Expr::TypeOf { operand, .. } => lint_expr(operand, out),
249
- Expr::JsxElement { props, children, .. } => {
249
+ Expr::JsxElement {
250
+ props, children, ..
251
+ } => {
250
252
  for pr in props {
251
253
  match pr {
252
254
  tishlang_ast::JsxProp::Attr { value, .. } => {
@@ -19,7 +19,7 @@ Binary: `target/release/tish-lsp` (stdio LSP).
19
19
 
20
20
  ## Client configuration
21
21
 
22
- See the [Tish docs — Language server](https://tishlang.github.io/tish-docs/reference/language-server/) and [Editor setup](https://tishlang.github.io/tish-docs/getting-started/editor/).
22
+ See the [Tish docs — Language server](https://tishlang.com/docs/reference/language-server/) and [Editor setup](https://tishlang.com/docs/getting-started/editor/).
23
23
 
24
24
  ## Developing
25
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tishlang/tish",
3
- "version": "1.3.8",
3
+ "version": "1.4.2",
4
4
  "description": "Tish - minimal TS/JS-compatible language. Run, REPL, build to native or other targets.",
5
5
  "license": "PIF",
6
6
  "repository": {
@@ -29,13 +29,6 @@
29
29
  "engines": {
30
30
  "node": ">=22"
31
31
  },
32
- "devDependencies": {
33
- "@semantic-release/commit-analyzer": "^13.0.1",
34
- "@semantic-release/github": "^12.0.6",
35
- "@semantic-release/npm": "^13.1.5",
36
- "@semantic-release/release-notes-generator": "^14.1.0",
37
- "semantic-release": "^25.0.3"
38
- },
39
32
  "keywords": [
40
33
  "tish",
41
34
  "language",
Binary file
Binary file
Binary file
Binary file
Binary file