harmonyc 0.16.4 → 0.18.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/README.md +13 -13
- package/package.json +7 -2
- package/cli/cli.d.ts +0 -2
- package/cli/cli.js +0 -32
- package/cli/run.d.ts +0 -2
- package/cli/run.js +0 -21
- package/cli/watch.d.ts +0 -1
- package/cli/watch.js +0 -35
- package/code_generator/VitestGenerator.d.ts +0 -31
- package/code_generator/VitestGenerator.js +0 -212
- package/code_generator/outFile.d.ts +0 -17
- package/code_generator/outFile.js +0 -51
- package/code_generator/test_phrases.d.ts +0 -11
- package/code_generator/test_phrases.js +0 -27
- package/compiler/compile.d.ts +0 -9
- package/compiler/compile.js +0 -32
- package/compiler/compiler.d.ts +0 -9
- package/compiler/compiler.js +0 -48
- package/filenames/filenames.d.ts +0 -3
- package/filenames/filenames.js +0 -17
- package/model/Router.d.ts +0 -22
- package/model/Router.js +0 -54
- package/model/model.d.ts +0 -205
- package/model/model.js +0 -480
- package/optimizations/autoLabel/autoLabel.d.ts +0 -2
- package/optimizations/autoLabel/autoLabel.js +0 -15
- package/parser/lexer.d.ts +0 -21
- package/parser/lexer.js +0 -123
- package/parser/lexer_rules.d.ts +0 -33
- package/parser/lexer_rules.js +0 -74
- package/parser/parser.d.ts +0 -18
- package/parser/parser.js +0 -76
- package/util/indent.d.ts +0 -1
- package/util/indent.js +0 -5
- package/util/iterators.d.ts +0 -1
- package/util/iterators.js +0 -5
- package/util/xmur3.d.ts +0 -1
- package/util/xmur3.js +0 -12
- package/vitest/index.d.ts +0 -10
- package/vitest/index.js +0 -69
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Harmony Code
|
|
2
2
|
|
|
3
|
-
A test design & BDD tool that helps you separate
|
|
3
|
+
A test design & BDD tool that helps you separate _what_ you test and _how_ you automate it. You write test cases in a simple easy-to-read format, and then automate them with Vitest.
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
@@ -10,23 +10,24 @@ You need to have Node.js installed. Then you can install Harmony Code in your pr
|
|
|
10
10
|
npm install harmonyc
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Then add it to your `vitest.config.js` or `vite.config.js` file, and
|
|
13
|
+
Then add it as a plugin to your `vitest.config.js` or `vite.config.js` file, and make sure to include your `.harmony` files:
|
|
14
14
|
|
|
15
15
|
```js
|
|
16
16
|
import harmony from 'harmonyc/vitest'
|
|
17
17
|
|
|
18
18
|
export default {
|
|
19
|
-
plugins: [harmony(
|
|
19
|
+
plugins: [harmony()],
|
|
20
|
+
include: ['src/**/*.harmony'],
|
|
20
21
|
}
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
This will run .harmony files in vitest.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
## VSCode plugin
|
|
27
|
+
|
|
28
|
+
Harmony Code has a [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=harmony-ac.harmony-code) that supports syntax highlighting.
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
Harmony Code is compatible with Vitest's VSCode plugin, so you can run and debug tests from the editor.
|
|
30
31
|
|
|
31
32
|
## Syntax
|
|
32
33
|
|
|
@@ -77,12 +78,12 @@ becomes
|
|
|
77
78
|
|
|
78
79
|
```javascript
|
|
79
80
|
test('T1 - strings', async () => {
|
|
80
|
-
const P = new Phrases()
|
|
81
|
-
await P.When_hello_(
|
|
81
|
+
const P = new Phrases()
|
|
82
|
+
await P.When_hello_('John')
|
|
82
83
|
})
|
|
83
84
|
test('T2 - code fragment', async () => {
|
|
84
|
-
const P = new Phrases()
|
|
85
|
-
await P.When_greet__times(3)
|
|
85
|
+
const P = new Phrases()
|
|
86
|
+
await P.When_greet__times(3)
|
|
86
87
|
})
|
|
87
88
|
```
|
|
88
89
|
|
|
@@ -136,7 +137,6 @@ test('T2 - store result in variable', (context) => {
|
|
|
136
137
|
})
|
|
137
138
|
```
|
|
138
139
|
|
|
139
|
-
|
|
140
140
|
## License
|
|
141
141
|
|
|
142
142
|
MIT
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harmonyc",
|
|
3
3
|
"description": "Harmony Code - model-driven BDD for Vitest",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"author": "Bernát Kalló",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"harmonyc": "./cli.js"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"."
|
|
12
|
+
],
|
|
10
13
|
"exports": {
|
|
11
14
|
"./vitest": {
|
|
12
15
|
"types": "./vitest/index.d.ts",
|
|
@@ -24,7 +27,9 @@
|
|
|
24
27
|
"dependencies": {
|
|
25
28
|
"fast-glob": "^3.3.2",
|
|
26
29
|
"tinyrainbow": "1",
|
|
27
|
-
"typescript-parsec": "0.3.4"
|
|
30
|
+
"typescript-parsec": "0.3.4"
|
|
31
|
+
},
|
|
32
|
+
"optionalDependencies": {
|
|
28
33
|
"watcher": "^2.3.1"
|
|
29
34
|
},
|
|
30
35
|
"license": "MIT",
|
package/cli/cli.d.ts
DELETED
package/cli/cli.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { compileFiles } from "../compiler/compiler.js";
|
|
3
|
-
import { parseArgs } from 'node:util';
|
|
4
|
-
import { watchFiles } from "./watch.js";
|
|
5
|
-
import { run, runWatch } from "./run.js";
|
|
6
|
-
const args = parseArgs({
|
|
7
|
-
options: {
|
|
8
|
-
help: { type: 'boolean' },
|
|
9
|
-
watch: { type: 'boolean', short: 'w' },
|
|
10
|
-
run: { type: 'boolean', short: 'r' },
|
|
11
|
-
},
|
|
12
|
-
allowPositionals: true,
|
|
13
|
-
});
|
|
14
|
-
if (args.positionals.length === 0 || args.values.help) {
|
|
15
|
-
console.error('Usage: harmonyc <input files glob pattern>');
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
;
|
|
19
|
-
(async () => {
|
|
20
|
-
if (args.values.watch) {
|
|
21
|
-
const outFns = await watchFiles(args.positionals);
|
|
22
|
-
if (args.values.run) {
|
|
23
|
-
runWatch(outFns);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
const { outFns } = await compileFiles(args.positionals);
|
|
28
|
-
if (args.values.run) {
|
|
29
|
-
run(outFns);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
})();
|
package/cli/run.d.ts
DELETED
package/cli/run.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { exec, spawn } from 'child_process';
|
|
2
|
-
function runCommand(patterns) {
|
|
3
|
-
return `npx vitest run ${args(patterns)}`;
|
|
4
|
-
}
|
|
5
|
-
function runWatchCommand(patterns) {
|
|
6
|
-
return `npx vitest ${args(patterns)}`;
|
|
7
|
-
}
|
|
8
|
-
function args(patterns) {
|
|
9
|
-
return patterns.map((s) => JSON.stringify(s)).join(' ');
|
|
10
|
-
}
|
|
11
|
-
export function run(patterns) {
|
|
12
|
-
var _a, _b;
|
|
13
|
-
const cmd = runCommand(patterns);
|
|
14
|
-
const p = exec(cmd, { cwd: process.cwd() });
|
|
15
|
-
(_a = p.stdout) === null || _a === void 0 ? void 0 : _a.pipe(process.stdout);
|
|
16
|
-
(_b = p.stderr) === null || _b === void 0 ? void 0 : _b.pipe(process.stderr);
|
|
17
|
-
}
|
|
18
|
-
export function runWatch(patterns) {
|
|
19
|
-
const cmd = runWatchCommand(patterns);
|
|
20
|
-
spawn(cmd, { cwd: process.cwd(), stdio: 'inherit', shell: true });
|
|
21
|
-
}
|
package/cli/watch.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function watchFiles(patterns: string[]): Promise<string[]>;
|
package/cli/watch.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import Watcher from 'watcher';
|
|
2
|
-
import { compileFile, compileFiles } from "../compiler/compiler.js";
|
|
3
|
-
export async function watchFiles(patterns) {
|
|
4
|
-
const { fns, outFns } = await compileFiles(patterns);
|
|
5
|
-
for (const file of fns) {
|
|
6
|
-
const watcher = new Watcher(file, { debounce: 20, ignoreInitial: true });
|
|
7
|
-
watcher.on('all', async () => {
|
|
8
|
-
var _a;
|
|
9
|
-
try {
|
|
10
|
-
await compileFile(file);
|
|
11
|
-
}
|
|
12
|
-
catch (e) {
|
|
13
|
-
process.stdout.write(`\n`);
|
|
14
|
-
console.log((_a = e.message) !== null && _a !== void 0 ? _a : e);
|
|
15
|
-
process.stdout.write(`\n`);
|
|
16
|
-
}
|
|
17
|
-
logger.log(`Compiled ${file}`);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
return outFns;
|
|
21
|
-
}
|
|
22
|
-
const logger = {
|
|
23
|
-
last: '',
|
|
24
|
-
n: 0,
|
|
25
|
-
log(msg) {
|
|
26
|
-
if (msg === this.last) {
|
|
27
|
-
process.stdout.write(`\r${msg} ${++this.n}x`);
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
process.stdout.write(`\r${msg}`);
|
|
31
|
-
this.last = msg;
|
|
32
|
-
this.n = 1;
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Action, CodeGenerator, ErrorResponse, Feature, Phrase, Response, SaveToVariable, SetVariable, Test, TestGroup } from '../model/model.ts';
|
|
2
|
-
import { OutFile } from './outFile.ts';
|
|
3
|
-
export declare class VitestGenerator implements CodeGenerator {
|
|
4
|
-
private tf;
|
|
5
|
-
private sf;
|
|
6
|
-
static error(message: string, stack: string): string;
|
|
7
|
-
framework: string;
|
|
8
|
-
phraseFns: Map<string, Phrase>;
|
|
9
|
-
currentFeatureName: string;
|
|
10
|
-
constructor(tf: OutFile, sf: OutFile);
|
|
11
|
-
feature(feature: Feature): void;
|
|
12
|
-
testGroup(g: TestGroup): void;
|
|
13
|
-
featureVars: Map<string, string>;
|
|
14
|
-
resultCount: number;
|
|
15
|
-
test(t: Test): void;
|
|
16
|
-
errorStep(action: Action, errorResponse: ErrorResponse): void;
|
|
17
|
-
extraArgs: string[];
|
|
18
|
-
step(action: Action, responses: Response[]): void;
|
|
19
|
-
private declareFeatureVariables;
|
|
20
|
-
phrase(p: Phrase): void;
|
|
21
|
-
setVariable(action: SetVariable): void;
|
|
22
|
-
saveToVariable(s: SaveToVariable, what?: string): void;
|
|
23
|
-
stringLiteral(text: string, { withVariables }: {
|
|
24
|
-
withVariables: boolean;
|
|
25
|
-
}): string;
|
|
26
|
-
codeLiteral(src: string): string;
|
|
27
|
-
private paramName;
|
|
28
|
-
stringParamDeclaration(index: number): string;
|
|
29
|
-
variantParamDeclaration(index: number): string;
|
|
30
|
-
}
|
|
31
|
-
export declare function functionName(phrase: Phrase): string;
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import { basename } from 'path';
|
|
2
|
-
import { Arg, Response, Word, } from "../model/model.js";
|
|
3
|
-
export class VitestGenerator {
|
|
4
|
-
static error(message, stack) {
|
|
5
|
-
return `const e = new SyntaxError(${str(message)});
|
|
6
|
-
e.stack = undefined;
|
|
7
|
-
throw e;
|
|
8
|
-
${stack ? `/* ${stack} */` : ''}`;
|
|
9
|
-
}
|
|
10
|
-
constructor(tf, sf) {
|
|
11
|
-
this.tf = tf;
|
|
12
|
-
this.sf = sf;
|
|
13
|
-
this.framework = 'vitest';
|
|
14
|
-
this.phraseFns = new Map();
|
|
15
|
-
this.currentFeatureName = '';
|
|
16
|
-
this.resultCount = 0;
|
|
17
|
-
this.extraArgs = [];
|
|
18
|
-
}
|
|
19
|
-
feature(feature) {
|
|
20
|
-
const phrasesModule = './' + basename(this.sf.name.replace(/.(js|ts)$/, ''));
|
|
21
|
-
const fn = (this.currentFeatureName = pascalCase(feature.name));
|
|
22
|
-
this.phraseFns = new Map();
|
|
23
|
-
if (this.framework === 'vitest') {
|
|
24
|
-
this.tf.print(`import { describe, test, expect } from "vitest";`);
|
|
25
|
-
}
|
|
26
|
-
if (feature.tests.length === 0) {
|
|
27
|
-
this.tf.print('');
|
|
28
|
-
this.tf.print(`describe.todo(${str(feature.name)});`);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
this.tf.print(`import ${fn}Phrases from ${str(phrasesModule)};`);
|
|
32
|
-
this.tf.print(``);
|
|
33
|
-
for (const item of feature.testGroups) {
|
|
34
|
-
item.toCode(this);
|
|
35
|
-
}
|
|
36
|
-
this.sf.print(`export default class ${pascalCase(feature.name)}Phrases {`);
|
|
37
|
-
this.sf.indent(() => {
|
|
38
|
-
for (const ph of this.phraseFns.keys()) {
|
|
39
|
-
const p = this.phraseFns.get(ph);
|
|
40
|
-
const params = p.args.map((a, i) => a.toDeclaration(this, i)).join(', ');
|
|
41
|
-
this.sf.print(`async ${ph}(${params}) {`);
|
|
42
|
-
this.sf.indent(() => {
|
|
43
|
-
this.sf.print(`throw new Error(${str(`Pending: ${ph}`)});`);
|
|
44
|
-
});
|
|
45
|
-
this.sf.print(`}`);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
this.sf.print(`};`);
|
|
49
|
-
}
|
|
50
|
-
testGroup(g) {
|
|
51
|
-
this.tf.print(`describe(${str(g.name)}, () => {`);
|
|
52
|
-
this.tf.indent(() => {
|
|
53
|
-
for (const item of g.items) {
|
|
54
|
-
item.toCode(this);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
this.tf.print('});');
|
|
58
|
-
}
|
|
59
|
-
test(t) {
|
|
60
|
-
this.resultCount = 0;
|
|
61
|
-
this.featureVars = new Map();
|
|
62
|
-
// avoid shadowing this import name
|
|
63
|
-
this.featureVars.set(new Object(), this.currentFeatureName);
|
|
64
|
-
this.tf.print(`test(${str(t.name)}, async (context) => {`);
|
|
65
|
-
this.tf.indent(() => {
|
|
66
|
-
this.tf.print(`context.task.meta.phrases ??= [];`);
|
|
67
|
-
for (const step of t.steps) {
|
|
68
|
-
step.toCode(this);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
this.tf.print('});');
|
|
72
|
-
}
|
|
73
|
-
errorStep(action, errorResponse) {
|
|
74
|
-
this.declareFeatureVariables([action]);
|
|
75
|
-
this.tf.print(`await expect(async () => {`);
|
|
76
|
-
this.tf.indent(() => {
|
|
77
|
-
action.toCode(this);
|
|
78
|
-
this.tf.print(`context.task.meta.phrases.push(${str(errorResponse.toSingleLineString())});`);
|
|
79
|
-
});
|
|
80
|
-
this.tf.print(`}).rejects.toThrow(${(errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.message) !== undefined
|
|
81
|
-
? str(errorResponse.message.text)
|
|
82
|
-
: ''});`);
|
|
83
|
-
}
|
|
84
|
-
step(action, responses) {
|
|
85
|
-
this.declareFeatureVariables([action, ...responses]);
|
|
86
|
-
if (responses.length === 0) {
|
|
87
|
-
action.toCode(this);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (action.isEmpty) {
|
|
91
|
-
for (const response of responses) {
|
|
92
|
-
response.toCode(this);
|
|
93
|
-
}
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
const res = `r${this.resultCount++ || ''}`;
|
|
97
|
-
this.tf.print(`const ${res} =`);
|
|
98
|
-
this.tf.indent(() => {
|
|
99
|
-
action.toCode(this);
|
|
100
|
-
try {
|
|
101
|
-
this.extraArgs = [res];
|
|
102
|
-
for (const response of responses) {
|
|
103
|
-
response.toCode(this);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
107
|
-
this.extraArgs = [];
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
declareFeatureVariables(phrases) {
|
|
112
|
-
for (const p of phrases) {
|
|
113
|
-
const feature = p.feature.name;
|
|
114
|
-
let f = this.featureVars.get(feature);
|
|
115
|
-
if (!f) {
|
|
116
|
-
f = toId(feature, abbrev, this.featureVars);
|
|
117
|
-
this.tf.print(`const ${f} = new ${pascalCase(feature)}Phrases(context);`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
phrase(p) {
|
|
122
|
-
const phrasefn = functionName(p);
|
|
123
|
-
if (!this.phraseFns.has(phrasefn))
|
|
124
|
-
this.phraseFns.set(phrasefn, p);
|
|
125
|
-
const f = this.featureVars.get(p.feature.name);
|
|
126
|
-
const args = p.args.map((a) => a.toCode(this));
|
|
127
|
-
args.push(...this.extraArgs);
|
|
128
|
-
if (p instanceof Response && p.parts.length === 1 && p.saveToVariable) {
|
|
129
|
-
return this.saveToVariable(p.saveToVariable);
|
|
130
|
-
}
|
|
131
|
-
this.tf.print(`(context.task.meta.phrases.push(${str(p.toString())}),`);
|
|
132
|
-
if (p instanceof Response && p.saveToVariable) {
|
|
133
|
-
this.saveToVariable(p.saveToVariable, '');
|
|
134
|
-
}
|
|
135
|
-
this.tf.print(`await ${f}.${functionName(p)}(${args.join(', ')}));`);
|
|
136
|
-
}
|
|
137
|
-
setVariable(action) {
|
|
138
|
-
this.tf.print(`(context.task.meta.variables ??= {})[${str(action.variableName)}] = ${action.value.toCode(this)};`);
|
|
139
|
-
}
|
|
140
|
-
saveToVariable(s, what = this.extraArgs[0] + ';') {
|
|
141
|
-
this.tf.print(`(context.task.meta.variables ??= {})[${str(s.variableName)}] = ${what}`.trimEnd());
|
|
142
|
-
}
|
|
143
|
-
stringLiteral(text, { withVariables }) {
|
|
144
|
-
if (withVariables && text.match(/\$\{/)) {
|
|
145
|
-
return templateStr(text).replace(/\\\$\{([^\s}]+)\}/g, (_, x) => `\${context.task.meta.variables?.[${str(x)}]}`);
|
|
146
|
-
}
|
|
147
|
-
return str(text);
|
|
148
|
-
}
|
|
149
|
-
codeLiteral(src) {
|
|
150
|
-
return src.replace(/\$\{([^\s}]+)\}/g, (_, x) => `context.task.meta.variables?.[${str(x)}]`);
|
|
151
|
-
}
|
|
152
|
-
paramName(index) {
|
|
153
|
-
return 'xyz'.charAt(index) || `a${index + 1}`;
|
|
154
|
-
}
|
|
155
|
-
stringParamDeclaration(index) {
|
|
156
|
-
return `${this.paramName(index)}: string`;
|
|
157
|
-
}
|
|
158
|
-
variantParamDeclaration(index) {
|
|
159
|
-
return `${this.paramName(index)}: any`;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
function str(s) {
|
|
163
|
-
if (s.includes('\n'))
|
|
164
|
-
return '\n' + templateStr(s);
|
|
165
|
-
let r = JSON.stringify(s);
|
|
166
|
-
return r;
|
|
167
|
-
}
|
|
168
|
-
function templateStr(s) {
|
|
169
|
-
return '`' + s.replace(/([`$\\])/g, '\\$1') + '`';
|
|
170
|
-
}
|
|
171
|
-
function capitalize(s) {
|
|
172
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
173
|
-
}
|
|
174
|
-
function toId(s, transform, previous) {
|
|
175
|
-
if (previous.has(s))
|
|
176
|
-
return previous.get(s);
|
|
177
|
-
let base = transform(s);
|
|
178
|
-
let id = base;
|
|
179
|
-
if ([...previous.values()].includes(id)) {
|
|
180
|
-
let i = 1;
|
|
181
|
-
while ([...previous.values()].includes(id + i))
|
|
182
|
-
i++;
|
|
183
|
-
id = base + i;
|
|
184
|
-
}
|
|
185
|
-
previous.set(s, id);
|
|
186
|
-
return id;
|
|
187
|
-
}
|
|
188
|
-
function words(s) {
|
|
189
|
-
return s.split(/[^0-9\p{L}]+/gu);
|
|
190
|
-
}
|
|
191
|
-
function pascalCase(s) {
|
|
192
|
-
return words(s).map(capitalize).join('');
|
|
193
|
-
}
|
|
194
|
-
function underscore(s) {
|
|
195
|
-
return words(s).join('_');
|
|
196
|
-
}
|
|
197
|
-
function abbrev(s) {
|
|
198
|
-
return words(s)
|
|
199
|
-
.map((x) => x.charAt(0).toUpperCase())
|
|
200
|
-
.join('');
|
|
201
|
-
}
|
|
202
|
-
export function functionName(phrase) {
|
|
203
|
-
const { kind } = phrase;
|
|
204
|
-
return ((kind === 'response' ? 'Then_' : 'When_') +
|
|
205
|
-
(phrase.parts
|
|
206
|
-
.flatMap((c) => c instanceof Word
|
|
207
|
-
? words(c.text).filter((x) => x)
|
|
208
|
-
: c instanceof Arg
|
|
209
|
-
? ['']
|
|
210
|
-
: [])
|
|
211
|
-
.join('_') || ''));
|
|
212
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Location } from '../model/model.ts';
|
|
2
|
-
import { SourceMapGenerator } from 'source-map-js';
|
|
3
|
-
export declare class OutFile {
|
|
4
|
-
name: string;
|
|
5
|
-
lines: string[];
|
|
6
|
-
level: number;
|
|
7
|
-
sm: SourceMapGenerator;
|
|
8
|
-
indentSpaces: number;
|
|
9
|
-
private currentLoc;
|
|
10
|
-
constructor(name: string);
|
|
11
|
-
indent(fn: () => void): void;
|
|
12
|
-
print(...lines: string[]): this;
|
|
13
|
-
loc({ location }: {
|
|
14
|
-
location?: Location;
|
|
15
|
-
}): this;
|
|
16
|
-
get value(): string;
|
|
17
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { SourceMapGenerator } from 'source-map-js';
|
|
2
|
-
export class OutFile {
|
|
3
|
-
constructor(name) {
|
|
4
|
-
this.name = name;
|
|
5
|
-
this.lines = [];
|
|
6
|
-
this.level = 0;
|
|
7
|
-
this.sm = new SourceMapGenerator();
|
|
8
|
-
this.indentSpaces = 2;
|
|
9
|
-
}
|
|
10
|
-
indent(fn) {
|
|
11
|
-
this.level++;
|
|
12
|
-
try {
|
|
13
|
-
fn();
|
|
14
|
-
}
|
|
15
|
-
finally {
|
|
16
|
-
this.level--;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
print(...lines) {
|
|
20
|
-
const l = this.lines.length;
|
|
21
|
-
this.lines.push(...lines.map((line) => ' '.repeat(this.level * this.indentSpaces) + line));
|
|
22
|
-
if (this.currentLoc)
|
|
23
|
-
for (const i of lines.keys()) {
|
|
24
|
-
this.sm.addMapping({
|
|
25
|
-
source: this.currentLoc.fileName,
|
|
26
|
-
original: {
|
|
27
|
-
line: this.currentLoc.line,
|
|
28
|
-
column: this.currentLoc.column,
|
|
29
|
-
},
|
|
30
|
-
generated: {
|
|
31
|
-
line: l + i,
|
|
32
|
-
column: this.level * this.indentSpaces,
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
return this;
|
|
37
|
-
}
|
|
38
|
-
loc({ location }) {
|
|
39
|
-
this.currentLoc = location;
|
|
40
|
-
return this;
|
|
41
|
-
}
|
|
42
|
-
get value() {
|
|
43
|
-
let res = this.lines.join('\n');
|
|
44
|
-
if (this.currentLoc) {
|
|
45
|
-
res +=
|
|
46
|
-
`\n\n//# sour` + // not for this file ;)
|
|
47
|
-
`ceMappingURL=data:application/json,${encodeURIComponent(this.sm.toString())}`;
|
|
48
|
-
}
|
|
49
|
-
return res;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export declare class TestPhrases {
|
|
2
|
-
private context;
|
|
3
|
-
constructor(context: any);
|
|
4
|
-
When_goodbye(): void;
|
|
5
|
-
When_hello(): string;
|
|
6
|
-
When_greet_(name: string): void;
|
|
7
|
-
Then__is_(x: string, y: string): Promise<void>;
|
|
8
|
-
Then_last_char(s: string): string;
|
|
9
|
-
Then_last_char_of_greeting(): any;
|
|
10
|
-
Then_(s: string, r: string): void;
|
|
11
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { expect } from 'vitest';
|
|
2
|
-
export class TestPhrases {
|
|
3
|
-
constructor(context) {
|
|
4
|
-
this.context = context;
|
|
5
|
-
}
|
|
6
|
-
When_goodbye() {
|
|
7
|
-
throw new Error('Goodbye, World!');
|
|
8
|
-
}
|
|
9
|
-
When_hello() {
|
|
10
|
-
return (this.context.task.meta.greeting = 'Hello!');
|
|
11
|
-
}
|
|
12
|
-
When_greet_(name) {
|
|
13
|
-
this.context.task.meta.greeting = `Hello, ${name}!`;
|
|
14
|
-
}
|
|
15
|
-
async Then__is_(x, y) {
|
|
16
|
-
expect(x).toBe(y);
|
|
17
|
-
}
|
|
18
|
-
Then_last_char(s) {
|
|
19
|
-
return s.slice(-1);
|
|
20
|
-
}
|
|
21
|
-
Then_last_char_of_greeting() {
|
|
22
|
-
return this.context.task.meta.greeting.slice(-1);
|
|
23
|
-
}
|
|
24
|
-
Then_(s, r) {
|
|
25
|
-
expect(s).toBe(r);
|
|
26
|
-
}
|
|
27
|
-
}
|
package/compiler/compile.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { OutFile } from '../code_generator/outFile.ts';
|
|
2
|
-
export interface CompiledFeature {
|
|
3
|
-
name: string;
|
|
4
|
-
code: Record<string, string>;
|
|
5
|
-
}
|
|
6
|
-
export declare function compileFeature(fileName: string, src: string): {
|
|
7
|
-
outFile: OutFile;
|
|
8
|
-
phrasesFile: OutFile;
|
|
9
|
-
};
|
package/compiler/compile.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { VitestGenerator } from "../code_generator/VitestGenerator.js";
|
|
2
|
-
import { OutFile } from "../code_generator/outFile.js";
|
|
3
|
-
import { parse } from "../parser/parser.js";
|
|
4
|
-
import { base, phrasesFileName, testFileName } from "../filenames/filenames.js";
|
|
5
|
-
import { Feature } from "../model/model.js";
|
|
6
|
-
import { basename } from 'node:path';
|
|
7
|
-
import { autoLabel } from "../optimizations/autoLabel/autoLabel.js";
|
|
8
|
-
export function compileFeature(fileName, src) {
|
|
9
|
-
const feature = new Feature(basename(base(fileName)));
|
|
10
|
-
try {
|
|
11
|
-
feature.root = parse(src);
|
|
12
|
-
}
|
|
13
|
-
catch (e) {
|
|
14
|
-
if (e.pos && e.errorMessage) {
|
|
15
|
-
e.message =
|
|
16
|
-
e.stack = `Error in ${fileName}:${e.pos.rowBegin}:${e.pos.columnBegin}\n${e.errorMessage}`;
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
e.stack = `Error in ${fileName}\n${e.stack}`;
|
|
20
|
-
}
|
|
21
|
-
throw e;
|
|
22
|
-
}
|
|
23
|
-
feature.root.setFeature(feature);
|
|
24
|
-
autoLabel(feature.root);
|
|
25
|
-
const testFn = testFileName(fileName);
|
|
26
|
-
const testFile = new OutFile(testFn);
|
|
27
|
-
const phrasesFn = phrasesFileName(fileName);
|
|
28
|
-
const phrasesFile = new OutFile(phrasesFn);
|
|
29
|
-
const cg = new VitestGenerator(testFile, phrasesFile);
|
|
30
|
-
feature.toCode(cg);
|
|
31
|
-
return { outFile: testFile, phrasesFile };
|
|
32
|
-
}
|
package/compiler/compiler.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare function compileFiles(pattern: string | string[]): Promise<{
|
|
2
|
-
fns: string[];
|
|
3
|
-
outFns: string[];
|
|
4
|
-
}>;
|
|
5
|
-
export declare function compileFile(fn: string): Promise<{
|
|
6
|
-
phrasesFileAction: string;
|
|
7
|
-
outFile: import("../code_generator/outFile.ts").OutFile;
|
|
8
|
-
phrasesFile: import("../code_generator/outFile.ts").OutFile;
|
|
9
|
-
} | undefined>;
|
package/compiler/compiler.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import glob from 'fast-glob';
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
-
import { resolve } from 'path';
|
|
4
|
-
import { compileFeature } from "./compile.js";
|
|
5
|
-
import { testFileName } from "../filenames/filenames.js";
|
|
6
|
-
import { VitestGenerator } from "../code_generator/VitestGenerator.js";
|
|
7
|
-
export async function compileFiles(pattern) {
|
|
8
|
-
var _a;
|
|
9
|
-
const fns = await glob(pattern);
|
|
10
|
-
if (!fns.length)
|
|
11
|
-
throw new Error(`No files found for pattern: ${String(pattern)}`);
|
|
12
|
-
const results = await Promise.allSettled(fns.map((fn) => compileFile(fn)));
|
|
13
|
-
const compiled = results.flatMap((r) => r.status === 'fulfilled' ? [r.value] : []);
|
|
14
|
-
const errored = results.flatMap((r) => r.status === 'rejected' && r.reason ? [r.reason] : []);
|
|
15
|
-
for (const error of errored) {
|
|
16
|
-
console.error((_a = error.message) !== null && _a !== void 0 ? _a : error);
|
|
17
|
-
}
|
|
18
|
-
console.log(`Compiled ${compiled.length} file${compiled.length === 1 ? '' : 's'}.`);
|
|
19
|
-
const features = compiled.filter((f) => f !== undefined);
|
|
20
|
-
const generated = features.filter((f) => f.phrasesFileAction === 'generated');
|
|
21
|
-
if (generated.length) {
|
|
22
|
-
console.log(`Generated ${generated.length} phrases file${generated.length === 1 ? '' : 's'}.`);
|
|
23
|
-
}
|
|
24
|
-
return { fns, outFns: features.map((f) => f.outFile.name) };
|
|
25
|
-
}
|
|
26
|
-
export async function compileFile(fn) {
|
|
27
|
-
var _a;
|
|
28
|
-
fn = resolve(fn);
|
|
29
|
-
const src = readFileSync(fn, 'utf8')
|
|
30
|
-
.toString()
|
|
31
|
-
.replace(/\r\n/g, '\n')
|
|
32
|
-
.replace(/\r/g, '\n');
|
|
33
|
-
try {
|
|
34
|
-
const { outFile, phrasesFile } = compileFeature(fn, src);
|
|
35
|
-
writeFileSync(outFile.name, outFile.value);
|
|
36
|
-
let phrasesFileAction = 'ignored';
|
|
37
|
-
if (!existsSync(phrasesFile.name)) {
|
|
38
|
-
phrasesFileAction = 'generated';
|
|
39
|
-
writeFileSync(phrasesFile.name, phrasesFile.value);
|
|
40
|
-
}
|
|
41
|
-
return { phrasesFileAction, outFile, phrasesFile };
|
|
42
|
-
}
|
|
43
|
-
catch (e) {
|
|
44
|
-
const outFileName = testFileName(fn);
|
|
45
|
-
writeFileSync(outFileName, VitestGenerator.error((_a = e.message) !== null && _a !== void 0 ? _a : `${e}`, e.stack));
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
}
|
package/filenames/filenames.d.ts
DELETED
package/filenames/filenames.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import glob from 'fast-glob';
|
|
2
|
-
const { globSync, convertPathToPattern } = glob;
|
|
3
|
-
export function base(fn) {
|
|
4
|
-
return fn.replace(/\.harmony(\.\w+)?$/i, '');
|
|
5
|
-
}
|
|
6
|
-
export function testFileName(fn) {
|
|
7
|
-
return base(fn) + '.test.mjs';
|
|
8
|
-
}
|
|
9
|
-
export function phrasesFileName(fn) {
|
|
10
|
-
const baseFn = base(fn);
|
|
11
|
-
const pattern = convertPathToPattern(baseFn);
|
|
12
|
-
const existing = globSync(`${pattern}.phrases.{tsx,jsx,ts,js}`);
|
|
13
|
-
if (existing.length) {
|
|
14
|
-
return existing.sort().at(-1);
|
|
15
|
-
}
|
|
16
|
-
return `${baseFn}.phrases.ts`;
|
|
17
|
-
}
|