harmonyc 0.12.2 → 0.14.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.
@@ -1,6 +1,11 @@
1
1
  import { basename } from 'path';
2
2
  import { Arg, Response, Word, } from "../model/model.js";
3
3
  export class VitestGenerator {
4
+ static error(message) {
5
+ return `const e = new SyntaxError(${str(message)});
6
+ e.stack = undefined;
7
+ throw e;`;
8
+ }
4
9
  constructor(tf, sf) {
5
10
  this.tf = tf;
6
11
  this.sf = sf;
@@ -15,7 +20,12 @@ export class VitestGenerator {
15
20
  const fn = (this.currentFeatureName = pascalCase(feature.name));
16
21
  this.phraseFns = new Map();
17
22
  if (this.framework === 'vitest') {
18
- this.tf.print(`import { describe, test, expect } from 'vitest';`);
23
+ this.tf.print(`import { describe, test, expect } from "vitest";`);
24
+ }
25
+ if (feature.tests.length === 0) {
26
+ this.tf.print('');
27
+ this.tf.print(`describe.todo(${str(feature.name)});`);
28
+ return;
19
29
  }
20
30
  this.tf.print(`import ${fn}Phrases from ${str(phrasesModule)};`);
21
31
  this.tf.print(``);
@@ -2,18 +2,21 @@ import glob from 'fast-glob';
2
2
  import { existsSync, readFileSync, writeFileSync } from 'fs';
3
3
  import { resolve } from 'path';
4
4
  import { compileFeature } from "./compile.js";
5
+ import { testFileName } from "../filenames/filenames.js";
6
+ import { VitestGenerator } from "../code_generator/VitestGenerator.js";
5
7
  export async function compileFiles(pattern) {
6
8
  var _a;
7
9
  const fns = await glob(pattern);
8
10
  if (!fns.length)
9
11
  throw new Error(`No files found for pattern: ${String(pattern)}`);
10
12
  const results = await Promise.allSettled(fns.map((fn) => compileFile(fn)));
11
- const features = results.flatMap((r) => r.status === 'fulfilled' ? [r.value] : []);
12
- const errors = results.flatMap((r) => r.status === 'rejected' ? [r.reason] : []);
13
- for (const error of errors) {
14
- console.log((_a = error.message) !== null && _a !== void 0 ? _a : error);
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);
15
17
  }
16
- console.log(`Compiled ${fns.length} file${fns.length === 1 ? '' : 's'}.`);
18
+ console.log(`Compiled ${compiled.length} file${compiled.length === 1 ? '' : 's'}.`);
19
+ const features = compiled.filter((f) => f !== undefined);
17
20
  const generated = features.filter((f) => f.phrasesFileAction === 'generated');
18
21
  if (generated.length) {
19
22
  console.log(`Generated ${generated.length} phrases file${generated.length === 1 ? '' : 's'}.`);
@@ -21,17 +24,25 @@ export async function compileFiles(pattern) {
21
24
  return { fns, outFns: features.map((f) => f.outFile.name) };
22
25
  }
23
26
  export async function compileFile(fn) {
27
+ var _a;
24
28
  fn = resolve(fn);
25
29
  const src = readFileSync(fn, 'utf8')
26
30
  .toString()
27
31
  .replace(/\r\n/g, '\n')
28
32
  .replace(/\r/g, '\n');
29
- const { outFile, phrasesFile } = compileFeature(fn, src);
30
- writeFileSync(outFile.name, outFile.value);
31
- let phrasesFileAction = 'ignored';
32
- if (!existsSync(phrasesFile.name)) {
33
- phrasesFileAction = 'generated';
34
- writeFileSync(phrasesFile.name, phrasesFile.value);
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}`));
46
+ return undefined;
35
47
  }
36
- return { phrasesFileAction, outFile, phrasesFile };
37
48
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harmonyc",
3
3
  "description": "Harmony Code - model-driven BDD for Vitest",
4
- "version": "0.12.2",
4
+ "version": "0.14.0",
5
5
  "author": "Bernát Kalló",
6
6
  "type": "module",
7
7
  "bin": {
package/parser/parser.js CHANGED
@@ -1,25 +1,48 @@
1
- import { alt_sc, apply, expectEOF, expectSingleResult, kright, opt_sc, rep_sc, seq, tok, list_sc, kleft, kmid, fail, nil, } from 'typescript-parsec';
1
+ import { alt_sc, apply, expectEOF, expectSingleResult, kright, opt_sc, rep_sc, seq, tok, list_sc, kleft, kmid, fail, nil, unableToConsumeToken, } from 'typescript-parsec';
2
2
  import { T, lexer } from "./lexer.js";
3
3
  import { Action, Response, CodeLiteral, StringLiteral, Section, Step, Docstring, Word, Label, ErrorResponse, SaveToVariable, SetVariable, } from "../model/model.js";
4
4
  export function parse(input, production = TEST_DESIGN) {
5
5
  const tokens = lexer.parse(input);
6
6
  return expectSingleResult(expectEOF(production.parse(tokens)));
7
7
  }
8
+ function anythingBut(kind) {
9
+ return {
10
+ parse(token) {
11
+ if (token === undefined)
12
+ return { successful: false, error: unableToConsumeToken(token) };
13
+ if (token.kind === kind)
14
+ return { successful: false, error: unableToConsumeToken(token) };
15
+ return {
16
+ candidates: [
17
+ {
18
+ firstToken: token,
19
+ nextToken: token.next,
20
+ result: token,
21
+ },
22
+ ],
23
+ successful: true,
24
+ error: undefined,
25
+ };
26
+ },
27
+ };
28
+ }
8
29
  export const NEWLINES = list_sc(tok(T.Newline), nil()), WORDS = apply(tok(T.Words), ({ text }) => new Word(text.trimEnd().split(/\s+/).join(' '))), DOUBLE_QUOTE_STRING = alt_sc(apply(tok(T.DoubleQuoteString), ({ text }) => new StringLiteral(JSON.parse(text))), seq(tok(T.UnclosedDoubleQuoteString), fail('unclosed double-quote string'))), BACKTICK_STRING = apply(tok(T.BacktickString), ({ text }) => new CodeLiteral(text.slice(1, -1))), DOCSTRING = kright(opt_sc(NEWLINES), apply(list_sc(tok(T.MultilineString), tok(T.Newline)), (lines) => new Docstring(lines.map(({ text }) => text.slice(2)).join('\n')))), ERROR_MARK = tok(T.ErrorMark), VARIABLE = apply(tok(T.Variable), ({ text }) => text.slice(2, -1)), PART = alt_sc(WORDS, DOUBLE_QUOTE_STRING, BACKTICK_STRING, DOCSTRING), PHRASE = rep_sc(PART), ARG = alt_sc(DOUBLE_QUOTE_STRING, BACKTICK_STRING, DOCSTRING), SET_VARIABLE = apply(seq(VARIABLE, ARG), ([variable, value]) => new SetVariable(variable, value)), ACTION = alt_sc(SET_VARIABLE, apply(PHRASE, (parts) => new Action(parts))), RESPONSE = apply(seq(PHRASE, opt_sc(VARIABLE)), ([parts, variable]) => new Response(parts, variable !== undefined ? new SaveToVariable(variable) : undefined)), ERROR_RESPONSE = apply(seq(ERROR_MARK, opt_sc(alt_sc(DOUBLE_QUOTE_STRING, DOCSTRING))), ([, parts]) => new ErrorResponse(parts)), SAVE_TO_VARIABLE = apply(VARIABLE, (variable) => new Response([], new SaveToVariable(variable))), ARROW = kright(opt_sc(NEWLINES), tok(T.ResponseArrow)), RESPONSE_ITEM = kright(ARROW, alt_sc(SAVE_TO_VARIABLE, ERROR_RESPONSE, RESPONSE)), STEP = apply(seq(ACTION, rep_sc(RESPONSE_ITEM)), ([action, responses]) => new Step(action, responses).setFork(true)), LABEL = apply(kleft(list_sc(PART, nil()), tok(T.Colon)), (words) => new Label(words.map((w) => w.toString()).join(' '))), SECTION = apply(LABEL, (text) => new Section(text)), BRANCH = alt_sc(SECTION, STEP), // section first, to make sure there is no colon after step
9
30
  DENTS = apply(alt_sc(tok(T.Plus), tok(T.Minus)), (seqOrFork) => {
10
31
  return {
11
32
  dent: (seqOrFork.text.length - 2) / 2,
12
33
  isFork: seqOrFork.kind === T.Plus,
13
34
  };
14
- }), LINE = apply(seq(DENTS, BRANCH), ([{ dent, isFork }, branch], [start, end]) => ({
35
+ }), NODE = apply(seq(DENTS, BRANCH), ([{ dent, isFork }, branch], [start, end]) => ({
15
36
  dent,
16
37
  branch: branch.setFork(isFork),
17
- })), TEST_DESIGN = kmid(rep_sc(NEWLINES), apply(opt_sc(list_sc(apply(LINE, (line, [start, end]) => ({ line, start, end })), NEWLINES)), (lines) => {
38
+ })), ANYTHING_BUT_NEWLINE = anythingBut(T.Newline), TEXT = apply(seq(tok(T.Words), rep_sc(ANYTHING_BUT_NEWLINE)), () => undefined), LINE = alt_sc(NODE, TEXT), TEST_DESIGN = kmid(rep_sc(NEWLINES), apply(opt_sc(list_sc(apply(LINE, (line, [start, end]) => ({ line, start, end })), NEWLINES)), (lines) => {
18
39
  const startDent = 0;
19
40
  let dent = startDent;
20
41
  const root = new Section(new Label(''));
21
42
  let parent = root;
22
43
  for (const { line, start } of lines !== null && lines !== void 0 ? lines : []) {
44
+ if (line === undefined)
45
+ continue;
23
46
  const { dent: d, branch } = line;
24
47
  if (Math.round(d) !== d) {
25
48
  throw new Error(`invalid odd indent of ${d * 2} at line ${start.pos.rowBegin}`);