char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport test extension typescript96879c9

main
6.6 KiB219 linesraw
1#[path = "common/lsp.rs"]
2#[allow(dead_code)]
3mod support;
4
5use std::path::Path;
6use std::process::{Command, Output};
7
8use support::{avril, backend, files};
9
10fn check(root: &Path, backend: &Path, args: &[&str]) -> Output {
11    Command::new(avril())
12        .current_dir(root)
13        .args([
14            "check",
15            &format!("--typescript={}", backend.display()),
16            "--offline",
17        ])
18        .args(args)
19        .output()
20        .unwrap()
21}
22
23#[test]
24fn generated_fjson_declarations_are_available_to_check() {
25    let Some(backend) = backend() else {
26        eprintln!("skipping: AVRIL_TEST_TYPESCRIPT is not set");
27        return;
28    };
29    let dir = tempfile::tempdir().unwrap();
30    let root = dir.path();
31    files(
32        root,
33        &[
34            ("avril.conf", "{}"),
35            (
36                "main.ts",
37                r#"
38                import {apply, parse, stringify} from 'avril:fjson';
39                const value: unknown = parse('answer: 42');
40                const source: string = stringify(value);
41                const result: string = apply(source, {answer: 43});
42                "#,
43            ),
44        ],
45    );
46    let clean = check(root, &backend, &["main.ts"]);
47    assert!(
48        clean.status.success(),
49        "{}{}",
50        String::from_utf8_lossy(&clean.stdout),
51        String::from_utf8_lossy(&clean.stderr),
52    );
53    files(
54        root,
55        &[(
56            "main.ts",
57            r#"
58            import {apply, parse} from 'avril:fjson';
59            const value: string = parse('answer: 42');
60            parse(123);
61            apply('', 123);
62            "#,
63        )],
64    );
65    let broken = check(root, &backend, &["main.ts"]);
66    let stdout = String::from_utf8(broken.stdout).unwrap();
67    assert!(!broken.status.success(), "{stdout}");
68    assert!(stdout.contains("error TS2322:"), "{stdout}");
69    assert!(stdout.contains("error TS2345:"), "{stdout}");
70}
71
72#[test]
73fn generated_test_declarations_are_available_to_check() {
74    let Some(backend) = backend() else {
75        eprintln!("skipping: AVRIL_TEST_TYPESCRIPT is not set");
76        return;
77    };
78    let dir = tempfile::tempdir().unwrap();
79    let root = dir.path();
80    files(
81        root,
82        &[
83            ("avril.conf", "{}"),
84            (
85                "main.ts",
86                r#"
87                import { test, type TestContext, type TestOptions } from "avril:test";
88                const options: TestOptions = { name: "options", fn: () => {} };
89                test(options);
90                test("named", async (t) => {
91                    const context: TestContext = t;
92                    const passed: boolean = await context.step("nested", (child) => {
93                        const name: string = child.name;
94                    });
95                });
96                test.only(options);
97                test.only("focused", () => {});
98                test.ignore(options);
99                test.ignore("ignored");
100                "#,
101            ),
102        ],
103    );
104    let clean = check(root, &backend, &["main.ts"]);
105    assert!(
106        clean.status.success(),
107        "{}{}",
108        String::from_utf8_lossy(&clean.stdout),
109        String::from_utf8_lossy(&clean.stderr),
110    );
111
112    files(
113        root,
114        &[(
115            "main.ts",
116            r#"
117        import { test, runTests } from "avril:test";
118        const result: string = test("name", () => {});
119    "#,
120        )],
121    );
122    let broken = check(root, &backend, &["main.ts"]);
123    let stdout = String::from_utf8(broken.stdout).unwrap();
124    assert!(!broken.status.success(), "{stdout}");
125    assert!(stdout.contains("error TS2322:"), "{stdout}");
126    assert!(stdout.contains("error TS2305:"), "{stdout}");
127
128    files(
129        root,
130        &[(
131            "main.ts",
132            r#"
133        // @ts-expect-error TypeScript suppression cannot grant private-module access.
134        import { op_test_event } from "ext:core/ops";
135    "#,
136        )],
137    );
138    let denied = check(root, &backend, &["main.ts"]);
139    let output = format!(
140        "{}{}",
141        String::from_utf8_lossy(&denied.stdout),
142        String::from_utf8_lossy(&denied.stderr),
143    );
144    assert!(!denied.status.success(), "{output}");
145    assert!(output.contains("Extension modules are private"), "{output}");
146}
147
148#[test]
149fn type_checks_the_module_graph_without_executing_it() {
150    let Some(backend) = backend() else {
151        eprintln!("skipping: AVRIL_TEST_TYPESCRIPT is not set");
152        return;
153    };
154    let dir = tempfile::tempdir().unwrap();
155    let root = dir.path();
156    files(
157        root,
158        &[
159            ("avril.conf", "{}"),
160            ("dep.ts", "export const value = 42 as const;"),
161            (
162                "main.ts",
163                "import {value} from \"./dep.ts\";\nconst checked: number = value;\nchecked;\nthrow Error(\"must not execute\");",
164            ),
165        ],
166    );
167    let clean = check(root, &backend, &["main.ts"]);
168    assert!(
169        clean.status.success(),
170        "{}",
171        String::from_utf8_lossy(&clean.stderr)
172    );
173    assert!(clean.stdout.is_empty());
174
175    files(
176        root,
177        &[(
178            "main.ts",
179            "import {value} from \"./dep.ts\";\nconst checked: string = value;\nchecked;\nthrow Error(\"must not execute\");",
180        )],
181    );
182    // The separate-value form works like `avril lsp --typescript PATH`.
183    let broken = Command::new(avril())
184        .current_dir(root)
185        .args([
186            "check",
187            "--typescript",
188            backend.to_str().unwrap(),
189            "--offline",
190            "main.ts",
191        ])
192        .output()
193        .unwrap();
194    let stdout = String::from_utf8(broken.stdout).unwrap();
195    assert!(!broken.status.success(), "{stdout}");
196    assert!(stdout.contains("main.ts:2:"), "{stdout}");
197    assert!(stdout.contains("error TS2322:"), "{stdout}");
198
199    files(root, &[("main.ts", "import \"./missing.ts\";")]);
200    let unresolved = check(root, &backend, &["main.ts"]);
201    let stdout = String::from_utf8(unresolved.stdout).unwrap();
202    assert!(!unresolved.status.success(), "{stdout}");
203    assert!(stdout.contains("./missing.ts"), "{stdout}");
204
205    // The entry grant does not extend to dependencies under `-0`: the
206    // denied dependency has no source in the program, but its diagnostic
207    // must still fail the check.
208    files(
209        root,
210        &[(
211            "main.ts",
212            "import {value} from \"./dep.ts\";\nconst checked: number = value;\nchecked;",
213        )],
214    );
215    let denied = check(root, &backend, &["-0", "main.ts"]);
216    let stdout = String::from_utf8(denied.stdout).unwrap();
217    assert!(!denied.status.success(), "{stdout}");
218    assert!(stdout.contains("PermissionDenied"), "{stdout}");
219}