redscript-mc 3.0.1 → 3.0.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/.github/workflows/ci.yml +1 -0
- package/README.md +119 -313
- package/README.zh.md +118 -314
- package/ROADMAP.md +5 -5
- package/dist/data/impl_test/function/counter/get.mcfunction +5 -0
- package/dist/data/impl_test/function/counter/inc.mcfunction +7 -0
- package/dist/data/impl_test/function/counter/new.mcfunction +4 -0
- package/dist/data/impl_test/function/load.mcfunction +1 -0
- package/dist/data/impl_test/function/test_impl.mcfunction +10 -0
- package/dist/data/minecraft/tags/function/load.json +5 -0
- package/dist/data/playground/function/load.mcfunction +1 -0
- package/dist/data/playground/function/start.mcfunction +4 -0
- package/dist/data/playground/function/start__say_macro_t1.mcfunction +1 -0
- package/dist/data/playground/function/stop.mcfunction +5 -0
- package/dist/data/playground/function/stop__say_macro_t0.mcfunction +1 -0
- package/dist/data/stdlib_queue8_test/function/__queue_append_apply.mcfunction +4 -0
- package/dist/data/stdlib_queue8_test/function/__queue_peek_apply.mcfunction +4 -0
- package/dist/data/stdlib_queue8_test/function/__queue_size_raw_apply.mcfunction +4 -0
- package/dist/data/stdlib_queue8_test/function/load.mcfunction +1 -0
- package/dist/data/stdlib_queue8_test/function/queue_clear.mcfunction +6 -0
- package/dist/data/stdlib_queue8_test/function/queue_empty__merge_1.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_empty__then_0.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_peek__merge_1.mcfunction +13 -0
- package/dist/data/stdlib_queue8_test/function/queue_peek__then_0.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_pop__merge_1.mcfunction +15 -0
- package/dist/data/stdlib_queue8_test/function/queue_pop__then_0.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_push__const_11.mcfunction +6 -0
- package/dist/data/stdlib_queue8_test/function/queue_push__const_22.mcfunction +6 -0
- package/dist/data/stdlib_queue8_test/function/queue_size.mcfunction +13 -0
- package/dist/data/stdlib_queue8_test/function/test_queue_push_and_size.mcfunction +13 -0
- package/dist/data/test/function/load.mcfunction +1 -0
- package/dist/data/test/function/say_at.mcfunction +6 -0
- package/dist/data/test/function/test.mcfunction +4 -0
- package/dist/pack.mcmeta +6 -0
- package/dist/package.json +1 -1
- package/dist/src/__tests__/formatter-extra.test.d.ts +7 -0
- package/dist/src/__tests__/formatter-extra.test.js +123 -0
- package/dist/src/__tests__/global-vars.test.d.ts +13 -0
- package/dist/src/__tests__/global-vars.test.js +156 -0
- package/dist/src/__tests__/lint/new-rules.test.d.ts +9 -0
- package/dist/src/__tests__/lint/new-rules.test.js +402 -0
- package/dist/src/__tests__/lsp-rename.test.d.ts +8 -0
- package/dist/src/__tests__/lsp-rename.test.js +157 -0
- package/dist/src/__tests__/mc-integration/say-fstring.test.d.ts +11 -0
- package/dist/src/__tests__/mc-integration/say-fstring.test.js +220 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-2.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-8.test.js +1 -1
- package/dist/src/__tests__/mc-syntax.test.js +4 -1
- package/dist/src/__tests__/monomorphize-coverage.test.d.ts +9 -0
- package/dist/src/__tests__/monomorphize-coverage.test.js +204 -0
- package/dist/src/__tests__/optimizer-cse.test.d.ts +7 -0
- package/dist/src/__tests__/optimizer-cse.test.js +226 -0
- package/dist/src/__tests__/parser.test.js +4 -13
- package/dist/src/__tests__/repl-server-extra.test.js +6 -7
- package/dist/src/__tests__/repl-server.test.js +5 -7
- package/dist/src/__tests__/stdlib/queue.test.js +6 -6
- package/dist/src/cli.js +0 -0
- package/dist/src/lexer/index.js +2 -1
- package/dist/src/lint/index.d.ts +12 -5
- package/dist/src/lint/index.js +730 -5
- package/dist/src/lsp/main.js +0 -0
- package/dist/src/mc-test/client.d.ts +21 -0
- package/dist/src/mc-test/client.js +34 -0
- package/dist/src/mir/lower.js +108 -6
- package/dist/src/optimizer/interprocedural.js +37 -2
- package/dist/src/parser/decl-parser.d.ts +19 -0
- package/dist/src/parser/decl-parser.js +323 -0
- package/dist/src/parser/expr-parser.d.ts +46 -0
- package/dist/src/parser/expr-parser.js +759 -0
- package/dist/src/parser/index.d.ts +8 -129
- package/dist/src/parser/index.js +13 -2262
- package/dist/src/parser/stmt-parser.d.ts +28 -0
- package/dist/src/parser/stmt-parser.js +577 -0
- package/dist/src/parser/type-parser.d.ts +20 -0
- package/dist/src/parser/type-parser.js +257 -0
- package/dist/src/parser/utils.d.ts +34 -0
- package/dist/src/parser/utils.js +141 -0
- package/docs/dev/README-mc-integration-tests.md +141 -0
- package/docs/lint-rules.md +162 -0
- package/docs/stdlib/bigint.md +2 -0
- package/editors/vscode/README.md +63 -41
- package/editors/vscode/out/extension.js +1881 -1776
- package/editors/vscode/out/lsp-server.js +4257 -3651
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/examples/loops-demo.mcrs +87 -0
- package/package.json +1 -1
- package/redscript-docs/docs/en/stdlib/advanced.md +629 -0
- package/redscript-docs/docs/en/stdlib/bigint.md +316 -0
- package/redscript-docs/docs/en/stdlib/bits.md +292 -0
- package/redscript-docs/docs/en/stdlib/bossbar.md +177 -0
- package/redscript-docs/docs/en/stdlib/calculus.md +289 -0
- package/redscript-docs/docs/en/stdlib/color.md +353 -0
- package/redscript-docs/docs/en/stdlib/combat.md +88 -0
- package/redscript-docs/docs/en/stdlib/cooldown.md +82 -0
- package/redscript-docs/docs/en/stdlib/dialog.md +155 -0
- package/redscript-docs/docs/en/stdlib/easing.md +558 -0
- package/redscript-docs/docs/en/stdlib/ecs.md +475 -0
- package/redscript-docs/docs/en/stdlib/effects.md +324 -0
- package/redscript-docs/docs/en/stdlib/events.md +3 -0
- package/redscript-docs/docs/en/stdlib/expr.md +45 -0
- package/redscript-docs/docs/en/stdlib/fft.md +141 -0
- package/redscript-docs/docs/en/stdlib/geometry.md +430 -0
- package/redscript-docs/docs/en/stdlib/graph.md +259 -0
- package/redscript-docs/docs/en/stdlib/heap.md +185 -0
- package/redscript-docs/docs/en/stdlib/interactions.md +179 -0
- package/redscript-docs/docs/en/stdlib/inventory.md +97 -0
- package/redscript-docs/docs/en/stdlib/linalg.md +557 -0
- package/redscript-docs/docs/en/stdlib/list.md +559 -0
- package/redscript-docs/docs/en/stdlib/map.md +140 -0
- package/redscript-docs/docs/en/stdlib/math.md +193 -0
- package/redscript-docs/docs/en/stdlib/math_hp.md +149 -0
- package/redscript-docs/docs/en/stdlib/matrix.md +403 -0
- package/redscript-docs/docs/en/stdlib/mobs.md +965 -0
- package/redscript-docs/docs/en/stdlib/noise.md +244 -0
- package/redscript-docs/docs/en/stdlib/ode.md +253 -0
- package/redscript-docs/docs/en/stdlib/parabola.md +342 -0
- package/redscript-docs/docs/en/stdlib/particles.md +311 -0
- package/redscript-docs/docs/en/stdlib/pathfind.md +255 -0
- package/redscript-docs/docs/en/stdlib/physics.md +493 -0
- package/redscript-docs/docs/en/stdlib/player.md +78 -0
- package/redscript-docs/docs/en/stdlib/quaternion.md +673 -0
- package/redscript-docs/docs/en/stdlib/queue.md +134 -0
- package/redscript-docs/docs/en/stdlib/random.md +223 -0
- package/redscript-docs/docs/en/stdlib/result.md +143 -0
- package/redscript-docs/docs/en/stdlib/scheduler.md +183 -0
- package/redscript-docs/docs/en/stdlib/set_int.md +190 -0
- package/redscript-docs/docs/en/stdlib/sets.md +101 -0
- package/redscript-docs/docs/en/stdlib/signal.md +400 -0
- package/redscript-docs/docs/en/stdlib/sort.md +104 -0
- package/redscript-docs/docs/en/stdlib/spawn.md +147 -0
- package/redscript-docs/docs/en/stdlib/state.md +142 -0
- package/redscript-docs/docs/en/stdlib/strings.md +154 -0
- package/redscript-docs/docs/en/stdlib/tags.md +3451 -0
- package/redscript-docs/docs/en/stdlib/teams.md +153 -0
- package/redscript-docs/docs/en/stdlib/timer.md +246 -0
- package/redscript-docs/docs/en/stdlib/vec.md +158 -0
- package/redscript-docs/docs/en/stdlib/world.md +298 -0
- package/redscript-docs/docs/zh/stdlib/advanced.md +615 -0
- package/redscript-docs/docs/zh/stdlib/bigint.md +316 -0
- package/redscript-docs/docs/zh/stdlib/bits.md +292 -0
- package/redscript-docs/docs/zh/stdlib/bossbar.md +170 -0
- package/redscript-docs/docs/zh/stdlib/calculus.md +287 -0
- package/redscript-docs/docs/zh/stdlib/color.md +353 -0
- package/redscript-docs/docs/zh/stdlib/combat.md +88 -0
- package/redscript-docs/docs/zh/stdlib/cooldown.md +84 -0
- package/redscript-docs/docs/zh/stdlib/dialog.md +152 -0
- package/redscript-docs/docs/zh/stdlib/easing.md +558 -0
- package/redscript-docs/docs/zh/stdlib/ecs.md +472 -0
- package/redscript-docs/docs/zh/stdlib/effects.md +324 -0
- package/redscript-docs/docs/zh/stdlib/events.md +3 -0
- package/redscript-docs/docs/zh/stdlib/expr.md +37 -0
- package/redscript-docs/docs/zh/stdlib/fft.md +128 -0
- package/redscript-docs/docs/zh/stdlib/geometry.md +430 -0
- package/redscript-docs/docs/zh/stdlib/graph.md +259 -0
- package/redscript-docs/docs/zh/stdlib/heap.md +185 -0
- package/redscript-docs/docs/zh/stdlib/interactions.md +160 -0
- package/redscript-docs/docs/zh/stdlib/inventory.md +94 -0
- package/redscript-docs/docs/zh/stdlib/linalg.md +543 -0
- package/redscript-docs/docs/zh/stdlib/list.md +561 -0
- package/redscript-docs/docs/zh/stdlib/map.md +132 -0
- package/redscript-docs/docs/zh/stdlib/math.md +193 -0
- package/redscript-docs/docs/zh/stdlib/math_hp.md +143 -0
- package/redscript-docs/docs/zh/stdlib/matrix.md +396 -0
- package/redscript-docs/docs/zh/stdlib/mobs.md +965 -0
- package/redscript-docs/docs/zh/stdlib/noise.md +244 -0
- package/redscript-docs/docs/zh/stdlib/ode.md +243 -0
- package/redscript-docs/docs/zh/stdlib/parabola.md +337 -0
- package/redscript-docs/docs/zh/stdlib/particles.md +307 -0
- package/redscript-docs/docs/zh/stdlib/pathfind.md +255 -0
- package/redscript-docs/docs/zh/stdlib/physics.md +493 -0
- package/redscript-docs/docs/zh/stdlib/player.md +78 -0
- package/redscript-docs/docs/zh/stdlib/quaternion.md +669 -0
- package/redscript-docs/docs/zh/stdlib/queue.md +124 -0
- package/redscript-docs/docs/zh/stdlib/random.md +222 -0
- package/redscript-docs/docs/zh/stdlib/result.md +147 -0
- package/redscript-docs/docs/zh/stdlib/scheduler.md +173 -0
- package/redscript-docs/docs/zh/stdlib/set_int.md +180 -0
- package/redscript-docs/docs/zh/stdlib/sets.md +107 -0
- package/redscript-docs/docs/zh/stdlib/signal.md +373 -0
- package/redscript-docs/docs/zh/stdlib/sort.md +104 -0
- package/redscript-docs/docs/zh/stdlib/spawn.md +142 -0
- package/redscript-docs/docs/zh/stdlib/state.md +134 -0
- package/redscript-docs/docs/zh/stdlib/strings.md +107 -0
- package/redscript-docs/docs/zh/stdlib/tags.md +3451 -0
- package/redscript-docs/docs/zh/stdlib/teams.md +150 -0
- package/redscript-docs/docs/zh/stdlib/timer.md +254 -0
- package/redscript-docs/docs/zh/stdlib/vec.md +158 -0
- package/redscript-docs/docs/zh/stdlib/world.md +289 -0
- package/src/__tests__/formatter-extra.test.ts +139 -0
- package/src/__tests__/global-vars.test.ts +171 -0
- package/src/__tests__/lint/new-rules.test.ts +437 -0
- package/src/__tests__/lsp-rename.test.ts +171 -0
- package/src/__tests__/mc-integration/say-fstring.test.ts +211 -0
- package/src/__tests__/mc-integration/stdlib-coverage-2.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-3.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-4.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-5.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-6.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-7.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-8.test.ts +1 -1
- package/src/__tests__/mc-syntax.test.ts +3 -0
- package/src/__tests__/monomorphize-coverage.test.ts +220 -0
- package/src/__tests__/optimizer-cse.test.ts +250 -0
- package/src/__tests__/parser.test.ts +4 -13
- package/src/__tests__/repl-server-extra.test.ts +6 -6
- package/src/__tests__/repl-server.test.ts +5 -6
- package/src/__tests__/stdlib/queue.test.ts +6 -6
- package/src/lexer/index.ts +2 -1
- package/src/lint/index.ts +713 -5
- package/src/mc-test/client.ts +40 -0
- package/src/mir/lower.ts +111 -2
- package/src/optimizer/interprocedural.ts +40 -2
- package/src/parser/decl-parser.ts +349 -0
- package/src/parser/expr-parser.ts +838 -0
- package/src/parser/index.ts +17 -2558
- package/src/parser/stmt-parser.ts +585 -0
- package/src/parser/type-parser.ts +276 -0
- package/src/parser/utils.ts +173 -0
- package/src/stdlib/queue.mcrs +19 -6
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for the 5 new redscript lint rules:
|
|
4
|
+
* no-dead-assignment
|
|
5
|
+
* prefer-match-exhaustive
|
|
6
|
+
* no-empty-catch
|
|
7
|
+
* naming-convention
|
|
8
|
+
* no-magic-numbers
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
const index_1 = require("../../lint/index");
|
|
12
|
+
function lint(source, opts = {}) {
|
|
13
|
+
return (0, index_1.lintString)(source, '<test>', 'test', opts);
|
|
14
|
+
}
|
|
15
|
+
function warnings(source, rule, opts = {}) {
|
|
16
|
+
return lint(source, opts).filter(w => w.rule === rule);
|
|
17
|
+
}
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Rule: no-dead-assignment
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
describe('no-dead-assignment', () => {
|
|
22
|
+
it('warns when a variable is assigned then immediately overwritten without being read', () => {
|
|
23
|
+
const src = `
|
|
24
|
+
fn foo(): void {
|
|
25
|
+
let x: int = 0;
|
|
26
|
+
x = 5;
|
|
27
|
+
x = 10;
|
|
28
|
+
say("done");
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
const w = warnings(src, 'no-dead-assignment');
|
|
32
|
+
expect(w.length).toBeGreaterThan(0);
|
|
33
|
+
expect(w[0].message).toContain('"x"');
|
|
34
|
+
expect(w[0].message).toContain('never read');
|
|
35
|
+
});
|
|
36
|
+
it('does NOT warn when each assignment is read before the next one', () => {
|
|
37
|
+
// x is read (return x + y) after all assignments
|
|
38
|
+
// y is read (return x + y)
|
|
39
|
+
const src = `
|
|
40
|
+
fn foo(): int {
|
|
41
|
+
let x: int = 5;
|
|
42
|
+
let y: int = x;
|
|
43
|
+
x = 10;
|
|
44
|
+
return x + y;
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
const w = warnings(src, 'no-dead-assignment');
|
|
48
|
+
// x is not overwritten without being read between assignments here:
|
|
49
|
+
// let x=5 → pending; y reads x → pending cleared; x=10 → no prior pending; return x reads x
|
|
50
|
+
expect(w).toHaveLength(0);
|
|
51
|
+
});
|
|
52
|
+
it('warns when let init is immediately overwritten without being read', () => {
|
|
53
|
+
// The initial let value is dead because x is reassigned before any read
|
|
54
|
+
const src = `
|
|
55
|
+
fn foo(): int {
|
|
56
|
+
let x: int = 0;
|
|
57
|
+
x = 42;
|
|
58
|
+
return x;
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
// let x=0 is dead (overwritten by x=42 before x is read)
|
|
62
|
+
const w = warnings(src, 'no-dead-assignment');
|
|
63
|
+
expect(w.length).toBeGreaterThan(0);
|
|
64
|
+
expect(w[0].message).toContain('"x"');
|
|
65
|
+
});
|
|
66
|
+
it('warns for multiple dead assignments in the same function', () => {
|
|
67
|
+
const src = `
|
|
68
|
+
fn bar(): void {
|
|
69
|
+
let a: int = 0;
|
|
70
|
+
let b: int = 0;
|
|
71
|
+
a = 1;
|
|
72
|
+
a = 2;
|
|
73
|
+
b = 3;
|
|
74
|
+
b = 4;
|
|
75
|
+
say("done");
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
const w = warnings(src, 'no-dead-assignment');
|
|
79
|
+
const msgs = w.map(warn => warn.message);
|
|
80
|
+
expect(msgs.some(m => m.includes('"a"'))).toBe(true);
|
|
81
|
+
expect(msgs.some(m => m.includes('"b"'))).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Rule: prefer-match-exhaustive
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
describe('prefer-match-exhaustive', () => {
|
|
88
|
+
it('warns when Option match is missing the None arm', () => {
|
|
89
|
+
const src = `
|
|
90
|
+
fn foo(x: Option<int>): void {
|
|
91
|
+
match x {
|
|
92
|
+
Some(v) => { say("got value"); }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
const w = warnings(src, 'prefer-match-exhaustive');
|
|
97
|
+
expect(w.length).toBeGreaterThan(0);
|
|
98
|
+
expect(w.some(warn => warn.message.includes('None'))).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
it('warns when Option match is missing the Some arm', () => {
|
|
101
|
+
const src = `
|
|
102
|
+
fn foo(x: Option<int>): void {
|
|
103
|
+
match x {
|
|
104
|
+
None => { say("nothing"); }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
`;
|
|
108
|
+
const w = warnings(src, 'prefer-match-exhaustive');
|
|
109
|
+
expect(w.length).toBeGreaterThan(0);
|
|
110
|
+
expect(w.some(warn => warn.message.includes('Some'))).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
it('does NOT warn when both Some and None arms are present', () => {
|
|
113
|
+
const src = `
|
|
114
|
+
fn foo(x: Option<int>): void {
|
|
115
|
+
match x {
|
|
116
|
+
Some(v) => { say("got value"); }
|
|
117
|
+
None => { say("nothing"); }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
`;
|
|
121
|
+
const w = warnings(src, 'prefer-match-exhaustive');
|
|
122
|
+
expect(w).toHaveLength(0);
|
|
123
|
+
});
|
|
124
|
+
it('does NOT warn when a wildcard arm is present', () => {
|
|
125
|
+
const src = `
|
|
126
|
+
fn foo(x: Option<int>): void {
|
|
127
|
+
match x {
|
|
128
|
+
Some(v) => { say("got"); }
|
|
129
|
+
_ => { say("other"); }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
const w = warnings(src, 'prefer-match-exhaustive');
|
|
134
|
+
expect(w).toHaveLength(0);
|
|
135
|
+
});
|
|
136
|
+
it('does NOT warn for non-Option integer match', () => {
|
|
137
|
+
const src = `
|
|
138
|
+
fn foo(x: int): void {
|
|
139
|
+
match x {
|
|
140
|
+
1 => { say("one"); }
|
|
141
|
+
2 => { say("two"); }
|
|
142
|
+
_ => { say("other"); }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
`;
|
|
146
|
+
const w = warnings(src, 'prefer-match-exhaustive');
|
|
147
|
+
expect(w).toHaveLength(0);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Rule: no-empty-catch
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
describe('no-empty-catch', () => {
|
|
154
|
+
it('warns when if let Some has an empty else block', () => {
|
|
155
|
+
const src = `
|
|
156
|
+
fn foo(x: Option<int>): void {
|
|
157
|
+
if let Some(v) = x {
|
|
158
|
+
say("got it");
|
|
159
|
+
} else {}
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
const w = warnings(src, 'no-empty-catch');
|
|
163
|
+
expect(w.length).toBeGreaterThan(0);
|
|
164
|
+
expect(w[0].message).toContain('None case is silently ignored');
|
|
165
|
+
});
|
|
166
|
+
it('does NOT warn when if let Some has no else block', () => {
|
|
167
|
+
const src = `
|
|
168
|
+
fn foo(x: Option<int>): void {
|
|
169
|
+
if let Some(v) = x {
|
|
170
|
+
say("got it");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
`;
|
|
174
|
+
const w = warnings(src, 'no-empty-catch');
|
|
175
|
+
expect(w).toHaveLength(0);
|
|
176
|
+
});
|
|
177
|
+
it('does NOT warn when else block has statements', () => {
|
|
178
|
+
const src = `
|
|
179
|
+
fn foo(x: Option<int>): void {
|
|
180
|
+
if let Some(v) = x {
|
|
181
|
+
say("got it");
|
|
182
|
+
} else {
|
|
183
|
+
say("nothing");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
`;
|
|
187
|
+
const w = warnings(src, 'no-empty-catch');
|
|
188
|
+
expect(w).toHaveLength(0);
|
|
189
|
+
});
|
|
190
|
+
it('warns when a match arm body is empty', () => {
|
|
191
|
+
const src = `
|
|
192
|
+
fn foo(x: Option<int>): void {
|
|
193
|
+
match x {
|
|
194
|
+
Some(v) => { say("ok"); }
|
|
195
|
+
None => {}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
const w = warnings(src, 'no-empty-catch');
|
|
200
|
+
expect(w.length).toBeGreaterThan(0);
|
|
201
|
+
expect(w[0].message).toContain('Empty match arm');
|
|
202
|
+
});
|
|
203
|
+
it('does NOT warn when all match arm bodies have statements', () => {
|
|
204
|
+
const src = `
|
|
205
|
+
fn foo(x: Option<int>): void {
|
|
206
|
+
match x {
|
|
207
|
+
Some(v) => { say("ok"); }
|
|
208
|
+
None => { say("none"); }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
`;
|
|
212
|
+
const w = warnings(src, 'no-empty-catch');
|
|
213
|
+
expect(w).toHaveLength(0);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Rule: naming-convention
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
describe('naming-convention', () => {
|
|
220
|
+
it('warns for snake_case variable names', () => {
|
|
221
|
+
const src = `
|
|
222
|
+
fn foo(): void {
|
|
223
|
+
let my_var: int = 5;
|
|
224
|
+
say("ok");
|
|
225
|
+
}
|
|
226
|
+
`;
|
|
227
|
+
const w = warnings(src, 'naming-convention');
|
|
228
|
+
expect(w.length).toBeGreaterThan(0);
|
|
229
|
+
expect(w[0].message).toContain('"my_var"');
|
|
230
|
+
expect(w[0].message).toContain('camelCase');
|
|
231
|
+
});
|
|
232
|
+
it('warns for UPPER_CASE variable names', () => {
|
|
233
|
+
const src = `
|
|
234
|
+
fn foo(): void {
|
|
235
|
+
let MAX_VAL: int = 5;
|
|
236
|
+
say("ok");
|
|
237
|
+
}
|
|
238
|
+
`;
|
|
239
|
+
const w = warnings(src, 'naming-convention');
|
|
240
|
+
expect(w.length).toBeGreaterThan(0);
|
|
241
|
+
expect(w[0].message).toContain('"MAX_VAL"');
|
|
242
|
+
});
|
|
243
|
+
it('does NOT warn for camelCase variable names', () => {
|
|
244
|
+
const src = `
|
|
245
|
+
fn foo(): void {
|
|
246
|
+
let myVar: int = 5;
|
|
247
|
+
let anotherOne: int = 10;
|
|
248
|
+
say("ok");
|
|
249
|
+
}
|
|
250
|
+
`;
|
|
251
|
+
const w = warnings(src, 'naming-convention');
|
|
252
|
+
expect(w).toHaveLength(0);
|
|
253
|
+
});
|
|
254
|
+
it('does NOT warn for single-letter variable names', () => {
|
|
255
|
+
const src = `
|
|
256
|
+
fn foo(): int {
|
|
257
|
+
let x: int = 5;
|
|
258
|
+
return x;
|
|
259
|
+
}
|
|
260
|
+
`;
|
|
261
|
+
const w = warnings(src, 'naming-convention');
|
|
262
|
+
expect(w).toHaveLength(0);
|
|
263
|
+
});
|
|
264
|
+
it('warns for snake_case loop bindings in foreach', () => {
|
|
265
|
+
const src = `
|
|
266
|
+
fn foo(): void {
|
|
267
|
+
foreach (my_item in @a) {
|
|
268
|
+
say("x");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
`;
|
|
272
|
+
const w = warnings(src, 'naming-convention');
|
|
273
|
+
expect(w.length).toBeGreaterThan(0);
|
|
274
|
+
expect(w[0].message).toContain('"my_item"');
|
|
275
|
+
});
|
|
276
|
+
it('does NOT warn for camelCase loop bindings', () => {
|
|
277
|
+
const src = `
|
|
278
|
+
fn foo(): void {
|
|
279
|
+
foreach (myItem in @a) {
|
|
280
|
+
say("x");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
`;
|
|
284
|
+
const w = warnings(src, 'naming-convention');
|
|
285
|
+
expect(w).toHaveLength(0);
|
|
286
|
+
});
|
|
287
|
+
it('warns for struct with lowercase start name', () => {
|
|
288
|
+
const src = `
|
|
289
|
+
struct myStruct { x: int }
|
|
290
|
+
|
|
291
|
+
fn foo(): void {
|
|
292
|
+
say("ok");
|
|
293
|
+
}
|
|
294
|
+
`;
|
|
295
|
+
const w = warnings(src, 'naming-convention');
|
|
296
|
+
expect(w.length).toBeGreaterThan(0);
|
|
297
|
+
expect(w[0].message).toContain('"myStruct"');
|
|
298
|
+
expect(w[0].message).toContain('PascalCase');
|
|
299
|
+
});
|
|
300
|
+
it('does NOT warn for correctly named PascalCase struct', () => {
|
|
301
|
+
const src = `
|
|
302
|
+
struct MyStruct { x: int }
|
|
303
|
+
|
|
304
|
+
fn foo(): void {
|
|
305
|
+
say("ok");
|
|
306
|
+
}
|
|
307
|
+
`;
|
|
308
|
+
const w = warnings(src, 'naming-convention');
|
|
309
|
+
expect(w).toHaveLength(0);
|
|
310
|
+
});
|
|
311
|
+
it('allows leading underscore in variable names', () => {
|
|
312
|
+
const src = `
|
|
313
|
+
fn foo(): void {
|
|
314
|
+
let _unused: int = 5;
|
|
315
|
+
say("ok");
|
|
316
|
+
}
|
|
317
|
+
`;
|
|
318
|
+
const w = warnings(src, 'naming-convention');
|
|
319
|
+
expect(w).toHaveLength(0);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// Rule: no-magic-numbers
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
describe('no-magic-numbers', () => {
|
|
326
|
+
it('warns for literal numbers other than 0 and 1', () => {
|
|
327
|
+
const src = `
|
|
328
|
+
fn foo(): void {
|
|
329
|
+
let x: int = 42;
|
|
330
|
+
say("ok");
|
|
331
|
+
}
|
|
332
|
+
`;
|
|
333
|
+
const w = warnings(src, 'no-magic-numbers');
|
|
334
|
+
expect(w.length).toBeGreaterThan(0);
|
|
335
|
+
expect(w[0].message).toContain('42');
|
|
336
|
+
expect(w[0].message).toContain('Magic number');
|
|
337
|
+
});
|
|
338
|
+
it('does NOT warn for 0', () => {
|
|
339
|
+
const src = `
|
|
340
|
+
fn foo(): int {
|
|
341
|
+
let x: int = 0;
|
|
342
|
+
return x;
|
|
343
|
+
}
|
|
344
|
+
`;
|
|
345
|
+
const w = warnings(src, 'no-magic-numbers');
|
|
346
|
+
expect(w).toHaveLength(0);
|
|
347
|
+
});
|
|
348
|
+
it('does NOT warn for 1', () => {
|
|
349
|
+
const src = `
|
|
350
|
+
fn foo(): int {
|
|
351
|
+
let x: int = 1;
|
|
352
|
+
return x;
|
|
353
|
+
}
|
|
354
|
+
`;
|
|
355
|
+
const w = warnings(src, 'no-magic-numbers');
|
|
356
|
+
expect(w).toHaveLength(0);
|
|
357
|
+
});
|
|
358
|
+
it('does NOT warn for numbers in const declarations', () => {
|
|
359
|
+
const src = `
|
|
360
|
+
const MAX_SIZE: int = 100;
|
|
361
|
+
|
|
362
|
+
fn foo(): void {
|
|
363
|
+
say("ok");
|
|
364
|
+
}
|
|
365
|
+
`;
|
|
366
|
+
const w = warnings(src, 'no-magic-numbers');
|
|
367
|
+
expect(w).toHaveLength(0);
|
|
368
|
+
});
|
|
369
|
+
it('respects custom allowedNumbers list', () => {
|
|
370
|
+
const src = `
|
|
371
|
+
fn foo(): void {
|
|
372
|
+
let x: int = 42;
|
|
373
|
+
let y: int = 100;
|
|
374
|
+
say("ok");
|
|
375
|
+
}
|
|
376
|
+
`;
|
|
377
|
+
// With 42 allowed, only 100 should warn
|
|
378
|
+
const w = warnings(src, 'no-magic-numbers', { allowedNumbers: [0, 1, 42] });
|
|
379
|
+
expect(w).toHaveLength(1);
|
|
380
|
+
expect(w[0].message).toContain('100');
|
|
381
|
+
});
|
|
382
|
+
it('warns for magic numbers used in expressions', () => {
|
|
383
|
+
const src = `
|
|
384
|
+
fn foo(x: int): bool {
|
|
385
|
+
return x > 255;
|
|
386
|
+
}
|
|
387
|
+
`;
|
|
388
|
+
const w = warnings(src, 'no-magic-numbers');
|
|
389
|
+
expect(w.length).toBeGreaterThan(0);
|
|
390
|
+
expect(w[0].message).toContain('255');
|
|
391
|
+
});
|
|
392
|
+
it('does NOT warn for 0 and 1 even without explicit allowedNumbers', () => {
|
|
393
|
+
const src = `
|
|
394
|
+
fn foo(x: int): bool {
|
|
395
|
+
return x > 0 && x < 1;
|
|
396
|
+
}
|
|
397
|
+
`;
|
|
398
|
+
const w = warnings(src, 'no-magic-numbers');
|
|
399
|
+
expect(w).toHaveLength(0);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
//# sourceMappingURL=new-rules.test.js.map
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for src/lsp/rename.ts — LSP rename symbol support
|
|
4
|
+
*
|
|
5
|
+
* Covers: findRenameRanges, buildRenameWorkspaceEdit,
|
|
6
|
+
* local variable renaming, function renaming, field renaming,
|
|
7
|
+
* nested scopes, and edge cases.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
const lexer_1 = require("../lexer");
|
|
11
|
+
const parser_1 = require("../parser");
|
|
12
|
+
const rename_1 = require("../lsp/rename");
|
|
13
|
+
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
|
|
14
|
+
function parse(source) {
|
|
15
|
+
const tokens = new lexer_1.Lexer(source).tokenize();
|
|
16
|
+
return new parser_1.Parser(tokens, source).parse('test');
|
|
17
|
+
}
|
|
18
|
+
function pos(line, character) {
|
|
19
|
+
return { line, character };
|
|
20
|
+
}
|
|
21
|
+
describe('findRenameRanges — local variables', () => {
|
|
22
|
+
it('finds all occurrences of a local variable', () => {
|
|
23
|
+
const src = 'fn test(): void {\n let x: int = 1;\n let y: int = x + 2;\n}';
|
|
24
|
+
const program = parse(src);
|
|
25
|
+
// Position on 'x' in 'let x: int = 1' (line 1, char 6)
|
|
26
|
+
const ranges = (0, rename_1.findRenameRanges)(src, program, pos(1, 6));
|
|
27
|
+
expect(ranges.length).toBe(2); // declaration + usage
|
|
28
|
+
});
|
|
29
|
+
it('returns empty for position not on a symbol', () => {
|
|
30
|
+
const src = 'fn test(): void {\n let x: int = 1;\n}';
|
|
31
|
+
const program = parse(src);
|
|
32
|
+
// Position on whitespace
|
|
33
|
+
const ranges = (0, rename_1.findRenameRanges)(src, program, pos(1, 0));
|
|
34
|
+
expect(ranges).toEqual([]);
|
|
35
|
+
});
|
|
36
|
+
it('handles variable used multiple times', () => {
|
|
37
|
+
const src = 'fn test(): void {\n let x: int = 1;\n let a: int = x;\n let b: int = x;\n let c: int = x;\n}';
|
|
38
|
+
const program = parse(src);
|
|
39
|
+
const ranges = (0, rename_1.findRenameRanges)(src, program, pos(1, 6));
|
|
40
|
+
expect(ranges.length).toBe(4); // 1 decl + 3 uses
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('findRenameRanges — functions', () => {
|
|
44
|
+
it('finds function name at declaration', () => {
|
|
45
|
+
const src = 'fn greet(): void {\n}\nfn main(): void {\n greet();\n}';
|
|
46
|
+
const program = parse(src);
|
|
47
|
+
// Position on 'greet' in fn declaration (line 0, char 3)
|
|
48
|
+
const ranges = (0, rename_1.findRenameRanges)(src, program, pos(0, 3));
|
|
49
|
+
expect(ranges.length).toBe(2); // declaration + call
|
|
50
|
+
});
|
|
51
|
+
it('finds function name at call site', () => {
|
|
52
|
+
const src = 'fn greet(): void {\n}\nfn main(): void {\n greet();\n}';
|
|
53
|
+
const program = parse(src);
|
|
54
|
+
// Position on 'greet()' call (line 3, char 2)
|
|
55
|
+
const ranges = (0, rename_1.findRenameRanges)(src, program, pos(3, 2));
|
|
56
|
+
expect(ranges.length).toBe(2);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('findRenameRanges — struct fields', () => {
|
|
60
|
+
it('finds field in struct declaration and member access', () => {
|
|
61
|
+
const src = [
|
|
62
|
+
'struct Point {',
|
|
63
|
+
' x: int,',
|
|
64
|
+
' y: int,',
|
|
65
|
+
'}',
|
|
66
|
+
'fn test(): Point {',
|
|
67
|
+
' let p: Point = Point { x: 1, y: 2 };',
|
|
68
|
+
' let v: int = p.x;',
|
|
69
|
+
' return Point { x: v, y: 0 };',
|
|
70
|
+
'}',
|
|
71
|
+
].join('\n');
|
|
72
|
+
const program = parse(src);
|
|
73
|
+
// Position on 'x' in struct field declaration (line 1, char 2)
|
|
74
|
+
const ranges = (0, rename_1.findRenameRanges)(src, program, pos(1, 2));
|
|
75
|
+
// Should find: field decl, struct literal 'x: 1', 'p.x', and struct literal 'x: v'
|
|
76
|
+
expect(ranges.length).toBeGreaterThanOrEqual(2);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('findRenameRanges — nested scopes', () => {
|
|
80
|
+
it('distinguishes variables in different scopes', () => {
|
|
81
|
+
const src = [
|
|
82
|
+
'fn test(): void {',
|
|
83
|
+
' let x: int = 1;',
|
|
84
|
+
' if true {',
|
|
85
|
+
' let x: int = 2;',
|
|
86
|
+
' let a: int = x;',
|
|
87
|
+
' }',
|
|
88
|
+
' let b: int = x;',
|
|
89
|
+
'}',
|
|
90
|
+
].join('\n');
|
|
91
|
+
const program = parse(src);
|
|
92
|
+
// Position on outer x (line 1, char 6)
|
|
93
|
+
const outerRanges = (0, rename_1.findRenameRanges)(src, program, pos(1, 6));
|
|
94
|
+
// Position on inner x (line 3, char 8)
|
|
95
|
+
const innerRanges = (0, rename_1.findRenameRanges)(src, program, pos(3, 8));
|
|
96
|
+
// They should refer to different symbols with different occurrence counts
|
|
97
|
+
expect(outerRanges.length).not.toBe(0);
|
|
98
|
+
expect(innerRanges.length).not.toBe(0);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('findRenameRanges — parameters', () => {
|
|
102
|
+
it('finds function parameter occurrences', () => {
|
|
103
|
+
const src = 'fn add(a: int, b: int): int {\n return a + b;\n}';
|
|
104
|
+
const program = parse(src);
|
|
105
|
+
// Position on parameter 'a' (line 0, char 7)
|
|
106
|
+
const ranges = (0, rename_1.findRenameRanges)(src, program, pos(0, 7));
|
|
107
|
+
expect(ranges.length).toBe(2); // param decl + usage in return
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe('buildRenameWorkspaceEdit', () => {
|
|
111
|
+
it('returns null when position is not on a symbol', () => {
|
|
112
|
+
const src = 'fn test(): void {}';
|
|
113
|
+
const program = parse(src);
|
|
114
|
+
const doc = vscode_languageserver_textdocument_1.TextDocument.create('file:///test.mcrs', 'redscript', 1, src);
|
|
115
|
+
const edit = (0, rename_1.buildRenameWorkspaceEdit)(doc, program, pos(0, 0), 'newName');
|
|
116
|
+
// 'fn' keyword at pos(0,0) — might or might not resolve
|
|
117
|
+
// If no symbol, should return null
|
|
118
|
+
if (edit === null) {
|
|
119
|
+
expect(edit).toBeNull();
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
expect(edit.changes).toBeDefined();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
it('returns workspace edit with correct URI and new name', () => {
|
|
126
|
+
const src = 'fn test(): void {\n let x: int = 1;\n let y: int = x;\n}';
|
|
127
|
+
const program = parse(src);
|
|
128
|
+
const doc = vscode_languageserver_textdocument_1.TextDocument.create('file:///test.mcrs', 'redscript', 1, src);
|
|
129
|
+
const edit = (0, rename_1.buildRenameWorkspaceEdit)(doc, program, pos(1, 6), 'newVar');
|
|
130
|
+
expect(edit).not.toBeNull();
|
|
131
|
+
expect(edit.changes).toBeDefined();
|
|
132
|
+
const changes = edit.changes['file:///test.mcrs'];
|
|
133
|
+
expect(changes.length).toBe(2);
|
|
134
|
+
expect(changes.every(c => c.newText === 'newVar')).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe('findRenameRanges — edge cases', () => {
|
|
138
|
+
it('handles single function with no body statements', () => {
|
|
139
|
+
const src = 'fn empty(): void {}';
|
|
140
|
+
const program = parse(src);
|
|
141
|
+
const ranges = (0, rename_1.findRenameRanges)(src, program, pos(0, 3));
|
|
142
|
+
expect(ranges.length).toBe(1); // just the declaration
|
|
143
|
+
});
|
|
144
|
+
it('handles const declarations', () => {
|
|
145
|
+
const src = 'fn test(): void {\n const MAX: int = 100;\n let x: int = MAX;\n}';
|
|
146
|
+
const program = parse(src);
|
|
147
|
+
const ranges = (0, rename_1.findRenameRanges)(src, program, pos(1, 8));
|
|
148
|
+
expect(ranges.length).toBe(2); // decl + use
|
|
149
|
+
});
|
|
150
|
+
it('handles assign expressions', () => {
|
|
151
|
+
const src = 'fn test(): void {\n let x: int = 1;\n x = 2;\n let y: int = x;\n}';
|
|
152
|
+
const program = parse(src);
|
|
153
|
+
const ranges = (0, rename_1.findRenameRanges)(src, program, pos(1, 6));
|
|
154
|
+
expect(ranges.length).toBe(3); // decl + assign + use
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
//# sourceMappingURL=lsp-rename.test.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedScript MC Integration Tests — say() with f-string
|
|
3
|
+
*
|
|
4
|
+
* Verifies that say(f"...{var}...") correctly compiles to a MC macro function
|
|
5
|
+
* ($say template with $(var) placeholders) and that variable interpolation
|
|
6
|
+
* works correctly at runtime via `function <helper> with storage rs:macro_args`.
|
|
7
|
+
*
|
|
8
|
+
* Run: npx jest say-fstring --forceExit
|
|
9
|
+
* With server: MC_SERVER_DIR=~/mc-test-server MC_PORT=25561 npx jest say-fstring --forceExit
|
|
10
|
+
*/
|
|
11
|
+
export {};
|