@typecad/expect 0.1.0-alpha.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 typecad0
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,345 @@
1
+ # @typecad/expect
2
+
3
+ Hardware test runner for [TypeCAD](../../README.md). Write vitest-style assertions in TypeScript; the framework compiles them to firmware, uploads to your board, reads the results over serial, and reports pass/fail in one command.
4
+
5
+ ```
6
+ cuttlefish-test v0.1.0
7
+
8
+ ✓ A0 analog read (2 tests)
9
+ ✓ reads a value in valid ADC range
10
+ ✓ reads less than mid-scale when grounded
11
+
12
+ ✓ A1 analog read (2 tests)
13
+ ✓ returns a non-negative value
14
+ ✓ is within 10-bit ADC range
15
+
16
+
17
+ Tests 4 passed (4)
18
+ Board @typecad/board-arduino-uno @ COM4
19
+ Time 18.97s
20
+
21
+ PASS All tests passed
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Table of Contents
27
+
28
+ - [How it works](#how-it-works)
29
+ - [Installation](#installation)
30
+ - [Writing tests](#writing-tests)
31
+ - [describe / it](#describe--it)
32
+ - [expect](#expect)
33
+ - [done](#done)
34
+ - [Numeric matchers](#numeric-matchers)
35
+ - [String matchers](#string-matchers)
36
+ - [Running tests](#running-tests)
37
+ - [CLI flags](#cli-flags)
38
+ - [cuttlefish.config.ts](#cuttlefishconfigts)
39
+ - [Architecture](#architecture)
40
+ - [Pipeline](#pipeline)
41
+ - [Serial protocol](#serial-protocol)
42
+ - [AST preprocessor](#ast-preprocessor)
43
+ - [Limitations](#limitations)
44
+
45
+ ---
46
+
47
+ ## How it works
48
+
49
+ 1. **Preprocess** — an AST transform rewrites the fluent test syntax into `Serial.print()` calls.
50
+ 2. **Transpile** — Cuttlefish converts the rewritten TypeScript to a C++ Arduino sketch.
51
+ 3. **Compile** — `arduino-cli compile` builds the sketch for the target board.
52
+ 4. **Upload** — `arduino-cli upload` flashes the firmware over serial.
53
+ 5. **Capture** — the host reads structured protocol lines from the serial port.
54
+ 6. **Evaluate** — assertion math runs on the host; the firmware only sends raw values.
55
+ 7. **Report** — results are printed in vitest-style output.
56
+
57
+ ---
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ npm install --save-dev @typecad/expect @typecad/cuttlefish
63
+ ```
64
+
65
+ `@typecad/expect` ships the `cuttlefish-test` CLI. It pairs with [`@typecad/cuttlefish`](https://github.com/justind000/typecode/tree/main/packages/cuttlefish), which transpiles your TypeScript test files to C++ for upload to hardware.
66
+
67
+ **Prerequisites:**
68
+
69
+ - [`arduino-cli`](https://arduino.github.io/arduino-cli/) installed and on `PATH`
70
+ - The target board core installed (`arduino-cli core install arduino:avr` for Uno)
71
+ - A USB serial port available
72
+
73
+ ---
74
+
75
+ ## Writing tests
76
+
77
+ Test files follow a fluent chaining style. Expectations can use direct values or zero-argument functions/IIFEs that return the value to assert.
78
+
79
+ ```typescript
80
+ // examples/my-sensor.test.ts
81
+ import { describe, done } from '@typecad/expect';
82
+ import { A0 } from '@typecad/board';
83
+
84
+ describe("A0 analog read")
85
+ .it("reads a value in valid ADC range")
86
+ .expect(A0.readAnalog()).toBeWithinRange(0, 1023)
87
+ .it("reads less than mid-scale when grounded")
88
+ .expect(A0.readAnalog()).toBeLessThan(512);
89
+
90
+ done();
91
+ ```
92
+
93
+ ### describe / it
94
+
95
+ `describe(name: string): Suite`
96
+
97
+ Opens a named test group. Returns a `Suite` that you chain `.it()` calls onto.
98
+
99
+ `suite.it(name: string): Suite`
100
+
101
+ Opens a named test case within the current group. Returns the same `Suite` for further chaining.
102
+
103
+ ### expect
104
+
105
+ `suite.expect(value: number): Expectation`
106
+
107
+ Captures a hardware value to be asserted. The argument must be a TypeCAD hardware expression (e.g. `A0.readAnalog()`, `pin.read()`) or a zero-argument function returning one. The preprocessor hoists hardware expressions so they are evaluated exactly once.
108
+
109
+ `suite.expectString(value: string): StringExpectation`
110
+
111
+ Same as `expect`, for string-producing expressions.
112
+
113
+ ### done
114
+
115
+ `done(): void`
116
+
117
+ Must be the **last statement** in every test file. Emits the `[TC:SUITE_END]` sentinel over serial and puts the firmware into an idle loop so the host runner knows collection is complete.
118
+
119
+ ---
120
+
121
+ ### Numeric matchers
122
+
123
+ All numeric matchers return the parent `Suite`, so you can continue the chain with `.it()`.
124
+
125
+ | Matcher | Passes when |
126
+ |---|---|
127
+ | `.toBe(n)` | `actual === n` |
128
+ | `.toBeGreaterThan(n)` | `actual > n` |
129
+ | `.toBeGreaterThanOrEqual(n)` | `actual >= n` |
130
+ | `.toBeLessThan(n)` | `actual < n` |
131
+ | `.toBeLessThanOrEqual(n)` | `actual <= n` |
132
+ | `.toBeCloseTo(n, precision)` | `\|actual − n\| < 10^(−precision)` |
133
+ | `.toBeWithinRange(min, max)` | `actual >= min && actual <= max` |
134
+ | `.toBeTruthy()` | `actual !== 0` |
135
+ | `.toBeFalsy()` | `actual === 0` |
136
+ | `.toNotBe(n)` | `actual !== n` |
137
+
138
+ ### String matchers
139
+
140
+ | Matcher | Passes when |
141
+ |---|---|
142
+ | `.toBe(s)` | `actual === s` |
143
+ | `.toContain(sub)` | `actual` contains `sub` |
144
+ | `.toHaveLength(n)` | `actual.length === n` |
145
+ | `.toNotBe(s)` | `actual !== s` |
146
+
147
+ ---
148
+
149
+ ## Running tests
150
+
151
+ ### CLI
152
+
153
+ ```bash
154
+ # Run a specific file
155
+ npx cuttlefish-test examples/my-sensor.test.ts
156
+
157
+ # Run all test files matched by config include patterns
158
+ npx cuttlefish-test
159
+
160
+ # Override the port at run-time
161
+ npx cuttlefish-test --port /dev/ttyACM0 examples/my-sensor.test.ts
162
+ ```
163
+
164
+ Or via the npm script defined in the root `package.json`:
165
+
166
+ ```bash
167
+ npm run test:hw
168
+ npm run test:hw -- --port COM4
169
+ ```
170
+
171
+ ### CLI flags
172
+
173
+ | Flag | Short | Default | Description |
174
+ |---|---|---|---|
175
+ | `--port <port>` | `-p` | from config | Serial port (e.g. `COM4`, `/dev/ttyACM0`) |
176
+ | `--board <pkg>` | `-b` | from config | Board package name override |
177
+ | `--build-target <fqbn>` | | from config | Framework-specific build target / FQBN override |
178
+ | `--baud <rate>` | | `115200` | Serial baud rate |
179
+ | `--timeout <ms>` | `-t` | `30000` | Serial read timeout in milliseconds |
180
+ | `--include <glob>` | `-i` | from config | Test file glob pattern (repeatable) |
181
+ | `--exclude <glob>` | `-x` | from config | Test file glob pattern to skip (repeatable) |
182
+ | `--verbose` | `-v` | `false` | Show raw serial output and per-assertion detail |
183
+ | `--help` | `-h` | | Print help and exit |
184
+
185
+ ### cuttlefish.config.ts
186
+
187
+ Add a `test` section to your project's `cuttlefish.config.ts` to avoid passing flags every time:
188
+
189
+ ```typescript
190
+ // cuttlefish.config.ts
191
+ import type { CuttlefishConfig } from '@typecad/cuttlefish/api';
192
+
193
+ const config: CuttlefishConfig = {
194
+ target: 'avr',
195
+ board: '@typecad/board-arduino-uno',
196
+ framework: '@typecad/framework-arduino',
197
+ frameworkData: {
198
+ buildTarget: 'arduino:avr:uno',
199
+ },
200
+
201
+ test: {
202
+ port: 'COM4', // serial port of the connected board
203
+ baudRate: 115200, // must match Serial.begin() in firmware
204
+ timeout: 30000, // ms to wait for SUITE_END before giving up
205
+ include: [ // glob patterns for test discovery
206
+ 'examples/**/*.test.ts',
207
+ 'tests/hardware/**/*.test.ts',
208
+ ],
209
+ exclude: [ // optional glob patterns to skip after discovery
210
+ 'tests/hardware/avr-only/**/*.test.ts',
211
+ ],
212
+ },
213
+ };
214
+
215
+ export default config;
216
+ ```
217
+
218
+ All `test` fields are optional and can be overridden by CLI flags.
219
+
220
+ ### Target-specific skips
221
+
222
+ Use a file-level comment when a test is valid only for some MCUs or framework targets. The runner checks these comments before preprocessing, compiling, or uploading.
223
+
224
+ ```typescript
225
+ // @typecad-skip-target esp32: ESP32 does not expose the AVR watchdog API.
226
+ ```
227
+
228
+ The inverse form skips every target except the listed ones:
229
+
230
+ ```typescript
231
+ // @typecad-only-target avr,megaavr: uses AVR watchdog registers.
232
+ ```
233
+
234
+ Targets are matched against `target`, the FQBN parts from `frameworkData.buildTarget` such as `esp32` in `esp32:esp32:esp32`, the full FQBN, and the board package name.
235
+
236
+ Skipped files are reported in the same style as Vitest:
237
+
238
+ ```text
239
+ ↓ tests/32-wdt.test.ts (skipped)
240
+
241
+ Tests 1 skipped (1)
242
+ Test Files 1 skipped (1)
243
+ PASS All tests passed
244
+ ```
245
+
246
+ Run with `--verbose` to print the skip reason from the directive.
247
+
248
+ ### Uno showcase validation example
249
+
250
+ Use the stock-Uno showcase in [examples/23-transpiler-showcase.ts](../../examples/23-transpiler-showcase.ts) for manual serial confirmation, then run the companion hardware test in [examples/24-uno-validation.test.ts](../../examples/24-uno-validation.test.ts) for automated checks.
251
+
252
+ Example flow:
253
+
254
+ ```bash
255
+ # 1. Compile and upload the serial-output showcase
256
+ npx cuttlefish src/23-transpiler-showcase.ts --compile --upload --port COM4
257
+
258
+ # 2. Run the on-hardware expect test against the connected Uno
259
+ npx cuttlefish-test examples/24-uno-validation.test.ts --port COM4
260
+ ```
261
+
262
+ This hybrid workflow is the recommended way to confirm that simple variables, arithmetic, arrays, enums, functions, GPIO, and analog input are behaving correctly on real Uno hardware.
263
+
264
+ ---
265
+
266
+ ## Architecture
267
+
268
+ ### Pipeline
269
+
270
+ ```
271
+ ┌─────────────────┐
272
+ │ test file .ts │ (user-authored TypeScript)
273
+ └────────┬────────┘
274
+ │ AST preprocessor (host, Node.js)
275
+
276
+ ┌─────────────────┐
277
+ │ rewritten .ts │ (Serial.print calls, hoisted hardware vars)
278
+ └────────┬────────┘
279
+ │ Cuttlefish transpiler
280
+
281
+ ┌─────────────────┐
282
+ │ .ino sketch │ (Arduino C++)
283
+ └────────┬────────┘
284
+ │ arduino-cli compile + upload
285
+
286
+ ┌─────────────────┐
287
+ │ running board │
288
+ └────────┬────────┘
289
+ │ serial port (structured text lines)
290
+
291
+ ┌─────────────────┐
292
+ │ host parser │ Node.js — builds result tree
293
+ └────────┬────────┘
294
+ │ evaluator
295
+
296
+ ┌─────────────────┐
297
+ │ pass / fail │ printed by reporter
298
+ └─────────────────┘
299
+ ```
300
+
301
+ ### Serial protocol
302
+
303
+ The firmware emits structured lines that the host runner filters from any other debug output:
304
+
305
+ ```
306
+ [TC:SUITE_START]
307
+ [TC:DESCRIBE:A0 analog read]
308
+ [TC:IT:reads a value in valid ADC range]
309
+ [TC:EXPECT:toBeWithinRange:0,1023:487]
310
+ [TC:IT:reads less than mid-scale when grounded]
311
+ [TC:EXPECT:toBeLessThan:512:487]
312
+ [TC:SUITE_END]
313
+ ```
314
+
315
+ All lines not beginning with `[TC:` are ignored, so `Serial.print()` debug statements in imported board libraries do not interfere with results.
316
+
317
+ **Assertion line format:** `[TC:EXPECT:<matcher>:<expected>:<actual>]`
318
+
319
+ - `expected` — the value(s) from the test source (e.g. `0,1023` for a range)
320
+ - `actual` — the raw value read from hardware
321
+
322
+ Assertion math (pass/fail, formatting) is computed entirely on the host, not in firmware.
323
+
324
+ ### AST preprocessor
325
+
326
+ The Cuttlefish transpiler cannot evaluate hardware calls (like `A0.readAnalog()`) when they are nested inside non-TypeCAD function calls — they lose their structured IR and become plain text. The preprocessor solves this before transpilation:
327
+
328
+ 1. Removes the `import { describe, done } from '@typecad/expect'` statement.
329
+ 2. Emits a `Serial.begin(...)` + `[TC:SUITE_START]` preamble once.
330
+ 3. Walks the fluent chain `describe(...).it(...).expect(expr).matcher(args)`.
331
+ 4. **Hoists** hardware expressions out of `.expect()` into a `const __tc_vN: number = expr;` declaration at the surrounding statement level.
332
+ 5. Replaces the `.expect(...).matcher(...)` chain with the appropriate `Serial.print("[TC:EXPECT:...]")` calls.
333
+ 6. Rewrites `done()` to `Serial.println("[TC:SUITE_END]") + while(true){delay(1000)}`.
334
+
335
+ The result is valid TypeCAD TypeScript with no nested hardware calls, ready for the standard transpiler.
336
+
337
+ ---
338
+
339
+ ## Limitations
340
+
341
+ - **No vitest-style callback suites** — groups and cases are defined by fluent chaining, not by `describe("name", () => { ... })`.
342
+ - **No async tests** — all timing is implicit (the board executes sequentially, the host waits on serial output).
343
+ - **Sequential execution only** — all describes in a file run once, in order, inside `setup()`. There is no `beforeEach`/`afterEach`.
344
+ - **One file per upload** — each test file produces one sketch and one upload cycle. Multiple test files run as separate upload+execute passes.
345
+ - **Number types only for hardware values** — TypeCAD maps numeric hardware readings to `int`/`float`. String expectations are for software string variables, not raw hardware reads.
@@ -0,0 +1,48 @@
1
+ import ts from 'typescript';
2
+ import type { PreprocessorContext } from './preprocessor.js';
3
+ /**
4
+ * Represents a single segment of a fluent describe().it().expect().matcher() chain.
5
+ */
6
+ export interface ChainSegment {
7
+ kind: 'describe' | 'it' | 'expect';
8
+ /** For describe/it: the label string. */
9
+ name?: string;
10
+ /** For expect: the actual-value expression text. */
11
+ actualExpr?: string;
12
+ /** For expect: the matcher name (toBe, toBeLessThan, etc.). */
13
+ matcher?: string;
14
+ /** For expect: matcher argument texts. */
15
+ matcherArgs?: string[];
16
+ /** True when the chain used .expectString() rather than .expect(). */
17
+ isStringExpect?: boolean;
18
+ /** For expect with function arg: named function definition to emit before the chain. */
19
+ extractedFn?: string;
20
+ }
21
+ /**
22
+ * Does this expression root in a `describe(...)` call?
23
+ */
24
+ export declare function isDescribeChain(expr: ts.Expression): boolean;
25
+ /**
26
+ * Walk a method chain to find the bottommost `describe(name)` call.
27
+ */
28
+ export declare function findDescribeRoot(expr: ts.Expression): ts.CallExpression | undefined;
29
+ /**
30
+ * Flatten a fluent chain AST into an ordered list of segments.
31
+ * Segments are collected bottom-up (describe → it → expect → matcher).
32
+ */
33
+ export declare function collectChainSegments(expr: ts.Expression, sf: ts.SourceFile, ctx: PreprocessorContext): ChainSegment[];
34
+ /** Result of extracting a function passed to expect(). */
35
+ interface ExtractedFnInfo {
36
+ fnDef: string;
37
+ fnCall: string;
38
+ }
39
+ /**
40
+ * Detect an arrow/function expression or IIFE passed to expect() and
41
+ * convert it into a named function definition + call.
42
+ *
43
+ * Handles both:
44
+ * `.expect(() => { ... })` — direct function
45
+ * `.expect((() => { ... })())` — IIFE
46
+ */
47
+ export declare function tryExtractFunction(node: ts.Expression, sf: ts.SourceFile, ctx: PreprocessorContext, isStringExpect?: boolean): ExtractedFnInfo | null;
48
+ export {};
@@ -0,0 +1,153 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @typecad/expect — Chain Collector
3
+ //
4
+ // Pure functions for detecting and extracting the fluent describe().it().expect()
5
+ // chain segments from a TypeScript AST. No I/O or protocol emission.
6
+ // ---------------------------------------------------------------------------
7
+ import ts from 'typescript';
8
+ // ---------------------------------------------------------------------------
9
+ // Chain detection
10
+ // ---------------------------------------------------------------------------
11
+ /**
12
+ * Does this expression root in a `describe(...)` call?
13
+ */
14
+ export function isDescribeChain(expr) {
15
+ return findDescribeRoot(expr) !== undefined;
16
+ }
17
+ /**
18
+ * Walk a method chain to find the bottommost `describe(name)` call.
19
+ */
20
+ export function findDescribeRoot(expr) {
21
+ if (ts.isCallExpression(expr)) {
22
+ if (ts.isIdentifier(expr.expression) && expr.expression.text === 'describe') {
23
+ return expr;
24
+ }
25
+ if (ts.isPropertyAccessExpression(expr.expression)) {
26
+ return findDescribeRoot(expr.expression.expression);
27
+ }
28
+ }
29
+ if (ts.isPropertyAccessExpression(expr)) {
30
+ return findDescribeRoot(expr.expression);
31
+ }
32
+ return undefined;
33
+ }
34
+ // ---------------------------------------------------------------------------
35
+ // Chain walking
36
+ // ---------------------------------------------------------------------------
37
+ /**
38
+ * Flatten a fluent chain AST into an ordered list of segments.
39
+ * Segments are collected bottom-up (describe → it → expect → matcher).
40
+ */
41
+ export function collectChainSegments(expr, sf, ctx) {
42
+ const segments = [];
43
+ collectSegmentsRecursive(expr, sf, segments, ctx);
44
+ return segments;
45
+ }
46
+ function collectSegmentsRecursive(expr, sf, segments, ctx) {
47
+ if (!ts.isCallExpression(expr))
48
+ return;
49
+ // Case 1: `describe("name")`
50
+ if (ts.isIdentifier(expr.expression) && expr.expression.text === 'describe') {
51
+ const name = extractStringArg(expr, 0, sf);
52
+ segments.push({ kind: 'describe', name: name ?? 'unnamed' });
53
+ return;
54
+ }
55
+ // Case 2: `receiver.method(args)` — a method in the chain
56
+ if (ts.isPropertyAccessExpression(expr.expression)) {
57
+ const methodName = expr.expression.name.text;
58
+ const receiver = expr.expression.expression;
59
+ if (methodName === 'it') {
60
+ collectSegmentsRecursive(receiver, sf, segments, ctx);
61
+ const name = extractStringArg(expr, 0, sf);
62
+ segments.push({ kind: 'it', name: name ?? 'unnamed' });
63
+ }
64
+ else if (methodName === 'expect' || methodName === 'expectString') {
65
+ collectSegmentsRecursive(receiver, sf, segments, ctx);
66
+ const argNode = expr.arguments[0];
67
+ const isString = methodName === 'expectString';
68
+ const fnInfo = argNode ? tryExtractFunction(argNode, sf, ctx, isString) : null;
69
+ if (fnInfo) {
70
+ segments.push({
71
+ kind: 'expect',
72
+ actualExpr: fnInfo.fnCall,
73
+ isStringExpect: isString,
74
+ extractedFn: fnInfo.fnDef,
75
+ });
76
+ }
77
+ else {
78
+ const actualExpr = argNode ? argNode.getText(sf) : '0';
79
+ segments.push({ kind: 'expect', actualExpr, isStringExpect: isString });
80
+ }
81
+ }
82
+ else {
83
+ // This is a matcher: toBe, toBeLessThan, etc.
84
+ collectSegmentsRecursive(receiver, sf, segments, ctx);
85
+ const last = segments[segments.length - 1];
86
+ if (last && last.kind === 'expect') {
87
+ last.matcher = methodName;
88
+ last.matcherArgs = [];
89
+ for (const arg of expr.arguments) {
90
+ last.matcherArgs.push(arg.getText(sf));
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ // ---------------------------------------------------------------------------
97
+ // Helpers
98
+ // ---------------------------------------------------------------------------
99
+ /** Extract the string literal value from argument at `index`, or `undefined`. */
100
+ function extractStringArg(call, index, sf) {
101
+ const arg = call.arguments[index];
102
+ if (!arg)
103
+ return undefined;
104
+ if (ts.isStringLiteral(arg))
105
+ return arg.text;
106
+ if (ts.isNoSubstitutionTemplateLiteral(arg))
107
+ return arg.text;
108
+ return arg.getText(sf);
109
+ }
110
+ /**
111
+ * Detect an arrow/function expression or IIFE passed to expect() and
112
+ * convert it into a named function definition + call.
113
+ *
114
+ * Handles both:
115
+ * `.expect(() => { ... })` — direct function
116
+ * `.expect((() => { ... })())` — IIFE
117
+ */
118
+ export function tryExtractFunction(node, sf, ctx, isStringExpect) {
119
+ // Unwrap IIFE
120
+ let fnNode = node;
121
+ if (ts.isCallExpression(node)) {
122
+ let callee = node.expression;
123
+ while (ts.isParenthesizedExpression(callee)) {
124
+ callee = callee.expression;
125
+ }
126
+ if (ts.isArrowFunction(callee) || ts.isFunctionExpression(callee)) {
127
+ fnNode = callee;
128
+ }
129
+ else {
130
+ return null;
131
+ }
132
+ }
133
+ if (ts.isParenthesizedExpression(fnNode)) {
134
+ fnNode = fnNode.expression;
135
+ }
136
+ if (!ts.isArrowFunction(fnNode) && !ts.isFunctionExpression(fnNode))
137
+ return null;
138
+ if (fnNode.parameters.length > 0)
139
+ return null;
140
+ const fnName = ctx.nextFn();
141
+ const body = fnNode.body;
142
+ let fnBody;
143
+ if (ts.isBlock(body)) {
144
+ fnBody = body.getText(sf);
145
+ }
146
+ else {
147
+ fnBody = `{ return ${body.getText(sf)}; }`;
148
+ }
149
+ const returnType = isStringExpect ? 'string' : 'number';
150
+ const fnDef = `function ${fnName}(): ${returnType} ${fnBody}`;
151
+ const fnCall = `${fnName}()`;
152
+ return { fnDef, fnCall };
153
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};