@tishlang/tish 1.3.8 → 1.5.0
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 +0 -0
- package/crates/tish/Cargo.toml +2 -2
- package/crates/tish/src/cli_help.rs +504 -0
- package/crates/tish/src/main.rs +76 -90
- package/crates/tish/src/repl_completion.rs +1 -1
- package/crates/tish/tests/integration_test.rs +48 -0
- package/crates/tish_build_utils/src/lib.rs +171 -1
- package/crates/tish_builtins/src/string.rs +248 -0
- package/crates/tish_bytecode/Cargo.toml +1 -0
- package/crates/tish_bytecode/src/compiler.rs +289 -66
- package/crates/tish_bytecode/src/opcode.rs +13 -3
- package/crates/tish_bytecode/src/peephole.rs +21 -16
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_compile/src/codegen.rs +214 -79
- package/crates/tish_compile/src/lib.rs +1 -1
- package/crates/tish_core/src/value.rs +1 -0
- package/crates/tish_eval/src/eval.rs +39 -1
- package/crates/tish_eval/src/lib.rs +1 -1
- package/crates/tish_lint/Cargo.toml +1 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +141 -23
- package/crates/tish_lint/src/lib.rs +3 -1
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_native/src/build.rs +48 -7
- package/crates/tish_native/src/lib.rs +8 -3
- package/crates/tish_runtime/src/lib.rs +4 -0
- package/crates/tish_vm/src/lib.rs +1 -1
- package/crates/tish_vm/src/vm.rs +155 -16
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/package.json +1 -8
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -515,6 +515,10 @@ impl Evaluator {
|
|
|
515
515
|
if from.starts_with("tish:") {
|
|
516
516
|
return self.load_builtin_module(from);
|
|
517
517
|
}
|
|
518
|
+
// Scoped native modules (e.g. `@tishlang/waterui`) registered via `TishNativeModule::virtual_builtin_modules`.
|
|
519
|
+
if self.virtual_builtins.borrow().get(from).is_some() {
|
|
520
|
+
return self.load_builtin_module(from);
|
|
521
|
+
}
|
|
518
522
|
let dir = self.current_dir.borrow().clone().ok_or_else(|| {
|
|
519
523
|
EvalError::Error("Cannot resolve imports: no current file directory (use run_file)".to_string())
|
|
520
524
|
})?;
|
|
@@ -1203,6 +1207,9 @@ impl Evaluator {
|
|
|
1203
1207
|
});
|
|
1204
1208
|
return Ok(Value::Number(found.unwrap_or(-1.0)));
|
|
1205
1209
|
}
|
|
1210
|
+
"lastIndexOf" => {
|
|
1211
|
+
return Ok(Self::string_last_index_of_eval(&arg_vals, s));
|
|
1212
|
+
}
|
|
1206
1213
|
"includes" => {
|
|
1207
1214
|
let search = match arg_vals.first() {
|
|
1208
1215
|
Some(Value::String(ss)) => ss.as_ref(),
|
|
@@ -1474,7 +1481,13 @@ impl Evaluator {
|
|
|
1474
1481
|
}
|
|
1475
1482
|
}
|
|
1476
1483
|
|
|
1477
|
-
// Fall through to normal function call
|
|
1484
|
+
// Fall through to normal function call. `get_prop` only implements `length` on
|
|
1485
|
+
// strings, so method calls would otherwise become `call_func(Null)` → Not a function.
|
|
1486
|
+
if let Value::String(s) = &obj {
|
|
1487
|
+
if method_name.as_ref() == "lastIndexOf" {
|
|
1488
|
+
return Ok(Self::string_last_index_of_eval(&arg_vals, s));
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1478
1491
|
let f = self.get_prop(&obj, method_name).map_err(EvalError::Error)?;
|
|
1479
1492
|
return self.call_func(&f, &arg_vals);
|
|
1480
1493
|
}
|
|
@@ -2638,6 +2651,31 @@ impl Evaluator {
|
|
|
2638
2651
|
Self::bind_destruct_pattern_scoped(&self.scope, pattern, value, mutable)
|
|
2639
2652
|
}
|
|
2640
2653
|
|
|
2654
|
+
/// `String.prototype.lastIndexOf` (interpreter). Kept as a helper so dispatch cannot fall
|
|
2655
|
+
/// through to [`Self::get_prop`] + [`Self::call_func`] for string receivers.
|
|
2656
|
+
fn string_last_index_of_eval(arg_vals: &[Value], receiver: &Arc<str>) -> Value {
|
|
2657
|
+
let search = match arg_vals.first() {
|
|
2658
|
+
Some(Value::String(ss)) => ss.as_ref(),
|
|
2659
|
+
_ => return Value::Number(-1.0),
|
|
2660
|
+
};
|
|
2661
|
+
let position_core: tishlang_core::Value = match arg_vals.get(1) {
|
|
2662
|
+
None => tishlang_core::Value::Number(f64::INFINITY),
|
|
2663
|
+
Some(Value::Null) => tishlang_core::Value::Null,
|
|
2664
|
+
Some(Value::Number(n)) => tishlang_core::Value::Number(*n),
|
|
2665
|
+
Some(Value::Bool(b)) => tishlang_core::Value::Bool(*b),
|
|
2666
|
+
Some(_) => tishlang_core::Value::Number(0.0),
|
|
2667
|
+
};
|
|
2668
|
+
let out = tishlang_builtins::string::last_index_of_str(
|
|
2669
|
+
receiver.as_ref(),
|
|
2670
|
+
search,
|
|
2671
|
+
&position_core,
|
|
2672
|
+
);
|
|
2673
|
+
match out {
|
|
2674
|
+
tishlang_core::Value::Number(n) => Value::Number(n),
|
|
2675
|
+
_ => Value::Number(-1.0),
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2641
2679
|
fn get_prop(&self, obj: &Value, key: &str) -> Result<Value, String> {
|
|
2642
2680
|
match obj {
|
|
2643
2681
|
Value::Object(map) => Ok(map.borrow().get(key).cloned().unwrap_or(Value::Null)),
|
|
@@ -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
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
d.
|
|
60
|
-
d.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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 {
|
|
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.
|
|
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
|
|
|
@@ -5,6 +5,29 @@ use std::path::Path;
|
|
|
5
5
|
|
|
6
6
|
use tishlang_compile::ResolvedNativeModule;
|
|
7
7
|
|
|
8
|
+
/// `tishlang_runtime` Cargo feature names (subset of CLI / compile feature names).
|
|
9
|
+
const RUNTIME_CARGO_FEATURES: &[&str] = &["http", "fs", "process", "regex", "ws"];
|
|
10
|
+
|
|
11
|
+
/// Map CLI/compile features to flags passed to `tishlang_runtime` in the temp crate's Cargo.toml.
|
|
12
|
+
/// `full` enables every optional runtime capability (matches `tish build --feature full` / LANGUAGE.md).
|
|
13
|
+
fn runtime_features_for_cargo(features: &[String]) -> Vec<String> {
|
|
14
|
+
let mut out = Vec::new();
|
|
15
|
+
for f in features {
|
|
16
|
+
if f == "full" {
|
|
17
|
+
for name in RUNTIME_CARGO_FEATURES {
|
|
18
|
+
if !out.iter().any(|x: &String| x == *name) {
|
|
19
|
+
out.push((*name).to_string());
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if RUNTIME_CARGO_FEATURES.contains(&f.as_str()) && !out.contains(f) {
|
|
25
|
+
out.push(f.clone());
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
out
|
|
29
|
+
}
|
|
30
|
+
|
|
8
31
|
pub fn build_via_cargo(
|
|
9
32
|
rust_code: &str,
|
|
10
33
|
native_modules: Vec<ResolvedNativeModule>,
|
|
@@ -19,15 +42,12 @@ pub fn build_via_cargo(
|
|
|
19
42
|
|
|
20
43
|
let runtime_path = tishlang_build_utils::find_runtime_path()?;
|
|
21
44
|
|
|
22
|
-
let runtime_features
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
.map(String::as_str)
|
|
26
|
-
.collect();
|
|
27
|
-
let features_str = if runtime_features.is_empty() {
|
|
45
|
+
let runtime_features = runtime_features_for_cargo(features);
|
|
46
|
+
let runtime_refs: Vec<&str> = runtime_features.iter().map(String::as_str).collect();
|
|
47
|
+
let features_str = if runtime_refs.is_empty() {
|
|
28
48
|
String::new()
|
|
29
49
|
} else {
|
|
30
|
-
format!(", features = {:?}",
|
|
50
|
+
format!(", features = {:?}", runtime_refs)
|
|
31
51
|
};
|
|
32
52
|
|
|
33
53
|
let needs_tokio = rust_code.contains("#[tokio::main]");
|
|
@@ -114,3 +134,24 @@ tishlang_runtime = {{ path = {:?}{} }}{}{}{}
|
|
|
114
134
|
Ok(())
|
|
115
135
|
}
|
|
116
136
|
|
|
137
|
+
#[cfg(test)]
|
|
138
|
+
mod tests {
|
|
139
|
+
use super::runtime_features_for_cargo;
|
|
140
|
+
|
|
141
|
+
#[test]
|
|
142
|
+
fn runtime_features_full_expands() {
|
|
143
|
+
let f = runtime_features_for_cargo(&["full".to_string()]);
|
|
144
|
+
assert!(f.contains(&"http".to_string()));
|
|
145
|
+
assert!(f.contains(&"fs".to_string()));
|
|
146
|
+
assert!(f.contains(&"process".to_string()));
|
|
147
|
+
assert!(f.contains(&"regex".to_string()));
|
|
148
|
+
assert!(f.contains(&"ws".to_string()));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[test]
|
|
152
|
+
fn runtime_features_merges_full_and_specific() {
|
|
153
|
+
let f = runtime_features_for_cargo(&["full".to_string(), "http".to_string()]);
|
|
154
|
+
assert_eq!(f.len(), 5);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
@@ -57,7 +57,7 @@ pub fn compile_to_native(
|
|
|
57
57
|
|
|
58
58
|
match backend {
|
|
59
59
|
Backend::Rust => {
|
|
60
|
-
let (rust_code, native_modules) = tishlang_compile::compile_project_full(
|
|
60
|
+
let (rust_code, native_modules, effective_features) = tishlang_compile::compile_project_full(
|
|
61
61
|
entry_path,
|
|
62
62
|
project_root,
|
|
63
63
|
features,
|
|
@@ -67,8 +67,13 @@ pub fn compile_to_native(
|
|
|
67
67
|
message: e.to_string(),
|
|
68
68
|
})?;
|
|
69
69
|
|
|
70
|
-
crate::build::build_via_cargo(
|
|
71
|
-
|
|
70
|
+
crate::build::build_via_cargo(
|
|
71
|
+
&rust_code,
|
|
72
|
+
native_modules,
|
|
73
|
+
output_path,
|
|
74
|
+
&effective_features,
|
|
75
|
+
)
|
|
76
|
+
.map_err(|e| NativeError { message: e })
|
|
72
77
|
}
|
|
73
78
|
Backend::Cranelift => {
|
|
74
79
|
let modules = tishlang_compile::resolve_project(entry_path, project_root)
|
|
@@ -71,6 +71,7 @@ pub use tishlang_builtins::string::{
|
|
|
71
71
|
repeat as string_repeat_impl,
|
|
72
72
|
pad_start as string_pad_start_impl,
|
|
73
73
|
pad_end as string_pad_end_impl,
|
|
74
|
+
last_index_of as string_last_index_of_impl,
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
// Wrapper functions to maintain API compatibility
|
|
@@ -119,6 +120,9 @@ pub fn string_char_code_at(s: &Value, idx: &Value) -> Value { string_char_code_a
|
|
|
119
120
|
pub fn string_repeat(s: &Value, count: &Value) -> Value { string_repeat_impl(s, count) }
|
|
120
121
|
pub fn string_pad_start(s: &Value, target_len: &Value, pad: &Value) -> Value { string_pad_start_impl(s, target_len, pad) }
|
|
121
122
|
pub fn string_pad_end(s: &Value, target_len: &Value, pad: &Value) -> Value { string_pad_end_impl(s, target_len, pad) }
|
|
123
|
+
pub fn string_last_index_of(s: &Value, search: &Value, position: &Value) -> Value {
|
|
124
|
+
string_last_index_of_impl(s, search, position)
|
|
125
|
+
}
|
|
122
126
|
|
|
123
127
|
/// Number.prototype.toFixed(digits) - format number with fixed decimal places (0-20)
|
|
124
128
|
pub fn number_to_fixed(n: &Value, digits: &Value) -> Value {
|