@application-kit/acceptance 0.0.0-stage → 0.1.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/CHANGELOG.md +13 -0
- package/dist/cjs/bin/application-scenarios.d.ts +2 -0
- package/dist/cjs/bin/application-scenarios.js +15 -0
- package/dist/cjs/bin/index.d.ts +1 -0
- package/dist/cjs/bin/index.js +17 -0
- package/dist/cjs/cadence/index.d.ts +1 -0
- package/dist/cjs/cadence/index.js +17 -0
- package/dist/cjs/cadence/resolve-cadence.d.ts +8 -0
- package/dist/cjs/cadence/resolve-cadence.js +18 -0
- package/dist/cjs/environments/environment.interface.d.ts +5 -0
- package/dist/cjs/environments/environment.interface.js +2 -0
- package/dist/cjs/environments/index.d.ts +3 -0
- package/dist/cjs/environments/index.js +19 -0
- package/dist/cjs/environments/resolve-environment.d.ts +10 -0
- package/dist/cjs/environments/resolve-environment.js +20 -0
- package/dist/cjs/environments/secrets.d.ts +4 -0
- package/dist/cjs/environments/secrets.js +14 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +18 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/scenarios/acceptance-class.d.ts +26 -0
- package/dist/cjs/scenarios/acceptance-class.js +100 -0
- package/dist/cjs/scenarios/board.d.ts +6 -0
- package/dist/cjs/scenarios/board.js +98 -0
- package/dist/cjs/scenarios/classes.d.ts +16 -0
- package/dist/cjs/scenarios/classes.js +48 -0
- package/dist/cjs/scenarios/classify.d.ts +35 -0
- package/dist/cjs/scenarios/classify.js +110 -0
- package/dist/cjs/scenarios/config.d.ts +17 -0
- package/dist/cjs/scenarios/config.js +67 -0
- package/dist/cjs/scenarios/coverage-data.d.ts +53 -0
- package/dist/cjs/scenarios/coverage-data.js +118 -0
- package/dist/cjs/scenarios/coverage-json.d.ts +2 -0
- package/dist/cjs/scenarios/coverage-json.js +94 -0
- package/dist/cjs/scenarios/coverage-open.d.ts +16 -0
- package/dist/cjs/scenarios/coverage-open.js +76 -0
- package/dist/cjs/scenarios/coverage-page.d.ts +2 -0
- package/dist/cjs/scenarios/coverage-page.js +108 -0
- package/dist/cjs/scenarios/coverage-removed.d.ts +13 -0
- package/dist/cjs/scenarios/coverage-removed.js +30 -0
- package/dist/cjs/scenarios/coverage.d.ts +26 -0
- package/dist/cjs/scenarios/coverage.js +91 -0
- package/dist/cjs/scenarios/dates.d.ts +8 -0
- package/dist/cjs/scenarios/dates.js +93 -0
- package/dist/cjs/scenarios/declared.d.ts +18 -0
- package/dist/cjs/scenarios/declared.js +69 -0
- package/dist/cjs/scenarios/features.d.ts +13 -0
- package/dist/cjs/scenarios/features.js +112 -0
- package/dist/cjs/scenarios/gherkin.d.ts +10 -0
- package/dist/cjs/scenarios/gherkin.js +75 -0
- package/dist/cjs/scenarios/git.d.ts +15 -0
- package/dist/cjs/scenarios/git.js +83 -0
- package/dist/cjs/scenarios/index.d.ts +27 -0
- package/dist/cjs/scenarios/index.js +43 -0
- package/dist/cjs/scenarios/inventory.d.ts +8 -0
- package/dist/cjs/scenarios/inventory.js +55 -0
- package/dist/cjs/scenarios/links.d.ts +4 -0
- package/dist/cjs/scenarios/links.js +99 -0
- package/dist/cjs/scenarios/lint-inputs.d.ts +12 -0
- package/dist/cjs/scenarios/lint-inputs.js +118 -0
- package/dist/cjs/scenarios/lint.d.ts +8 -0
- package/dist/cjs/scenarios/lint.js +94 -0
- package/dist/cjs/scenarios/projects.d.ts +21 -0
- package/dist/cjs/scenarios/projects.js +115 -0
- package/dist/cjs/scenarios/python-text.d.ts +11 -0
- package/dist/cjs/scenarios/python-text.js +95 -0
- package/dist/cjs/scenarios/remote.d.ts +6 -0
- package/dist/cjs/scenarios/remote.js +29 -0
- package/dist/cjs/scenarios/removed-list.d.ts +15 -0
- package/dist/cjs/scenarios/removed-list.js +62 -0
- package/dist/cjs/scenarios/report.d.ts +6 -0
- package/dist/cjs/scenarios/report.js +10 -0
- package/dist/cjs/scenarios/run.d.ts +10 -0
- package/dist/cjs/scenarios/run.js +145 -0
- package/dist/cjs/scenarios/task-links.d.ts +15 -0
- package/dist/cjs/scenarios/task-links.js +54 -0
- package/dist/esm/bin/application-scenarios.d.ts +2 -0
- package/dist/esm/bin/application-scenarios.js +10 -0
- package/dist/esm/bin/index.d.ts +1 -0
- package/dist/esm/bin/index.js +1 -0
- package/dist/esm/cadence/index.d.ts +1 -0
- package/dist/esm/cadence/index.js +1 -0
- package/dist/esm/cadence/resolve-cadence.d.ts +8 -0
- package/dist/esm/cadence/resolve-cadence.js +14 -0
- package/dist/esm/environments/environment.interface.d.ts +5 -0
- package/dist/esm/environments/environment.interface.js +1 -0
- package/dist/esm/environments/index.d.ts +3 -0
- package/dist/esm/environments/index.js +3 -0
- package/dist/esm/environments/resolve-environment.d.ts +10 -0
- package/dist/esm/environments/resolve-environment.js +16 -0
- package/dist/esm/environments/secrets.d.ts +4 -0
- package/dist/esm/environments/secrets.js +9 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/scenarios/acceptance-class.d.ts +26 -0
- package/dist/esm/scenarios/acceptance-class.js +93 -0
- package/dist/esm/scenarios/board.d.ts +6 -0
- package/dist/esm/scenarios/board.js +92 -0
- package/dist/esm/scenarios/classes.d.ts +16 -0
- package/dist/esm/scenarios/classes.js +42 -0
- package/dist/esm/scenarios/classify.d.ts +35 -0
- package/dist/esm/scenarios/classify.js +106 -0
- package/dist/esm/scenarios/config.d.ts +17 -0
- package/dist/esm/scenarios/config.js +58 -0
- package/dist/esm/scenarios/coverage-data.d.ts +53 -0
- package/dist/esm/scenarios/coverage-data.js +109 -0
- package/dist/esm/scenarios/coverage-json.d.ts +2 -0
- package/dist/esm/scenarios/coverage-json.js +90 -0
- package/dist/esm/scenarios/coverage-open.d.ts +16 -0
- package/dist/esm/scenarios/coverage-open.js +71 -0
- package/dist/esm/scenarios/coverage-page.d.ts +2 -0
- package/dist/esm/scenarios/coverage-page.js +104 -0
- package/dist/esm/scenarios/coverage-removed.d.ts +13 -0
- package/dist/esm/scenarios/coverage-removed.js +26 -0
- package/dist/esm/scenarios/coverage.d.ts +26 -0
- package/dist/esm/scenarios/coverage.js +84 -0
- package/dist/esm/scenarios/dates.d.ts +8 -0
- package/dist/esm/scenarios/dates.js +87 -0
- package/dist/esm/scenarios/declared.d.ts +18 -0
- package/dist/esm/scenarios/declared.js +62 -0
- package/dist/esm/scenarios/features.d.ts +13 -0
- package/dist/esm/scenarios/features.js +96 -0
- package/dist/esm/scenarios/gherkin.d.ts +10 -0
- package/dist/esm/scenarios/gherkin.js +68 -0
- package/dist/esm/scenarios/git.d.ts +15 -0
- package/dist/esm/scenarios/git.js +72 -0
- package/dist/esm/scenarios/index.d.ts +27 -0
- package/dist/esm/scenarios/index.js +27 -0
- package/dist/esm/scenarios/inventory.d.ts +8 -0
- package/dist/esm/scenarios/inventory.js +48 -0
- package/dist/esm/scenarios/links.d.ts +4 -0
- package/dist/esm/scenarios/links.js +95 -0
- package/dist/esm/scenarios/lint-inputs.d.ts +12 -0
- package/dist/esm/scenarios/lint-inputs.js +110 -0
- package/dist/esm/scenarios/lint.d.ts +8 -0
- package/dist/esm/scenarios/lint.js +89 -0
- package/dist/esm/scenarios/projects.d.ts +21 -0
- package/dist/esm/scenarios/projects.js +104 -0
- package/dist/esm/scenarios/python-text.d.ts +11 -0
- package/dist/esm/scenarios/python-text.js +81 -0
- package/dist/esm/scenarios/remote.d.ts +6 -0
- package/dist/esm/scenarios/remote.js +24 -0
- package/dist/esm/scenarios/removed-list.d.ts +15 -0
- package/dist/esm/scenarios/removed-list.js +57 -0
- package/dist/esm/scenarios/report.d.ts +6 -0
- package/dist/esm/scenarios/report.js +6 -0
- package/dist/esm/scenarios/run.d.ts +10 -0
- package/dist/esm/scenarios/run.js +141 -0
- package/dist/esm/scenarios/task-links.d.ts +15 -0
- package/dist/esm/scenarios/task-links.js +50 -0
- package/package.json +65 -3
- package/README.md +0 -3
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.where = exports.featureLanguage = exports.featureTags = exports.parseFeature = void 0;
|
|
4
|
+
const python_text_js_1 = require("./python-text.js");
|
|
5
|
+
const startFeature = (state) => {
|
|
6
|
+
state.featureTags = state.pendingTags;
|
|
7
|
+
state.ruleTags = [];
|
|
8
|
+
};
|
|
9
|
+
const startRule = (state) => {
|
|
10
|
+
state.ruleTags = state.pendingTags;
|
|
11
|
+
};
|
|
12
|
+
const addScenario = (state, line, number) => {
|
|
13
|
+
state.scenarios.push({
|
|
14
|
+
line: number,
|
|
15
|
+
tags: [...state.featureTags, ...state.ruleTags, ...state.pendingTags],
|
|
16
|
+
title: line.slice(line.indexOf(':') + 1).trim(),
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
const skipTags = () => {
|
|
20
|
+
};
|
|
21
|
+
const headings = [
|
|
22
|
+
[['Feature', 'Business Need', 'Ability', 'Функция', 'Функциональность', 'Функционал', 'Свойство'], startFeature],
|
|
23
|
+
[['Rule', 'Правило'], startRule],
|
|
24
|
+
[
|
|
25
|
+
[
|
|
26
|
+
'Scenario Outline',
|
|
27
|
+
'Scenario Template',
|
|
28
|
+
'Scenario',
|
|
29
|
+
'Example',
|
|
30
|
+
'Структура сценария',
|
|
31
|
+
'Шаблон сценария',
|
|
32
|
+
'Сценарий',
|
|
33
|
+
'Пример',
|
|
34
|
+
],
|
|
35
|
+
addScenario,
|
|
36
|
+
],
|
|
37
|
+
[['Examples', 'Scenarios', 'Примеры', 'Background', 'Предыстория', 'Контекст'], skipTags],
|
|
38
|
+
];
|
|
39
|
+
const headingOf = (line) => headings
|
|
40
|
+
.find(([words]) => words.some((word) => line.startsWith(`${word}:`)))?.[1];
|
|
41
|
+
const commentTail = /#.*/u;
|
|
42
|
+
const lineTags = (line) => line
|
|
43
|
+
.replace(commentTail, '')
|
|
44
|
+
.split(/\s/u)
|
|
45
|
+
.filter((tag) => tag.startsWith('@'))
|
|
46
|
+
.map((tag) => tag.slice(1));
|
|
47
|
+
const consumeLine = (state, line, number) => {
|
|
48
|
+
if (line.startsWith('@')) {
|
|
49
|
+
state.pendingTags = [...state.pendingTags, ...lineTags(line)];
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const heading = headingOf(line);
|
|
53
|
+
if (heading === undefined) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
heading(state, line, number);
|
|
57
|
+
state.pendingTags = [];
|
|
58
|
+
};
|
|
59
|
+
const parseState = (text) => {
|
|
60
|
+
const state = { featureTags: [], pendingTags: [], ruleTags: [], scenarios: [] };
|
|
61
|
+
for (const [index, raw] of (0, python_text_js_1.splitLines)(text).entries()) {
|
|
62
|
+
consumeLine(state, raw.trim(), index + 1);
|
|
63
|
+
}
|
|
64
|
+
return state;
|
|
65
|
+
};
|
|
66
|
+
const parseFeature = (text, file) => parseState(text).scenarios
|
|
67
|
+
.map((scenario) => ({ file, ...scenario }));
|
|
68
|
+
exports.parseFeature = parseFeature;
|
|
69
|
+
const featureTags = (text) => parseState(text).featureTags;
|
|
70
|
+
exports.featureTags = featureTags;
|
|
71
|
+
const languageLine = /^[\t ]*#[\t ]*language:[\t ]*(?<language>\S+)/mu;
|
|
72
|
+
const featureLanguage = (text) => languageLine.exec(text)?.groups?.language ?? 'en';
|
|
73
|
+
exports.featureLanguage = featureLanguage;
|
|
74
|
+
const where = (scenario) => `${scenario.file}:${scenario.line} «${scenario.title}»`;
|
|
75
|
+
exports.where = where;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface GitResultInterface {
|
|
2
|
+
readonly code: number;
|
|
3
|
+
readonly stderr: string;
|
|
4
|
+
readonly stdout: Buffer;
|
|
5
|
+
}
|
|
6
|
+
export interface RepoInterface {
|
|
7
|
+
readonly cwd: string;
|
|
8
|
+
readonly repo: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const currentRepo = ".";
|
|
11
|
+
export declare const runGit: (location: RepoInterface, gitArguments: readonly string[], input?: string) => Promise<GitResultInterface>;
|
|
12
|
+
export declare const stdoutText: (result: GitResultInterface) => string;
|
|
13
|
+
export declare const git: (cwd: string, repo: string, gitArguments: readonly string[]) => Promise<string | undefined>;
|
|
14
|
+
export declare const commonDirectory: (cwd: string, repo: string) => Promise<string | undefined>;
|
|
15
|
+
export declare const readAtRevision: (location: RepoInterface, revision: string, files: readonly string[]) => Promise<Map<string, string>>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readAtRevision = exports.commonDirectory = exports.git = exports.stdoutText = exports.runGit = exports.currentRepo = void 0;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const config_js_1 = require("./config.js");
|
|
11
|
+
const python_text_js_1 = require("./python-text.js");
|
|
12
|
+
exports.currentRepo = '.';
|
|
13
|
+
const decoder = new TextDecoder('utf-8');
|
|
14
|
+
const ignorePipeError = () => {
|
|
15
|
+
};
|
|
16
|
+
const runGit = async (location, gitArguments, input = '') => new Promise((resolve) => {
|
|
17
|
+
const child = (0, node_child_process_1.spawn)('git', ['-C', location.repo, ...gitArguments], { cwd: location.cwd });
|
|
18
|
+
const stdout = [];
|
|
19
|
+
const stderr = [];
|
|
20
|
+
child.stdout.on('data', (chunk) => {
|
|
21
|
+
stdout.push(chunk);
|
|
22
|
+
});
|
|
23
|
+
child.stderr.on('data', (chunk) => {
|
|
24
|
+
stderr.push(chunk);
|
|
25
|
+
});
|
|
26
|
+
child.stdin.on('error', ignorePipeError);
|
|
27
|
+
child.on('error', (error) => {
|
|
28
|
+
resolve({ code: -1, stderr: error.message, stdout: Buffer.alloc(0) });
|
|
29
|
+
});
|
|
30
|
+
child.on('close', (code) => {
|
|
31
|
+
resolve({ code: code ?? -1, stderr: Buffer.concat(stderr).toString('utf8'), stdout: Buffer.concat(stdout) });
|
|
32
|
+
});
|
|
33
|
+
child.stdin.end(input);
|
|
34
|
+
});
|
|
35
|
+
exports.runGit = runGit;
|
|
36
|
+
const stdoutText = (result) => (0, python_text_js_1.universalNewlines)(result.stdout.toString('utf8'));
|
|
37
|
+
exports.stdoutText = stdoutText;
|
|
38
|
+
const git = async (cwd, repo, gitArguments) => {
|
|
39
|
+
const done = await (0, exports.runGit)({ cwd, repo }, gitArguments);
|
|
40
|
+
return done.code === 0 ? (0, exports.stdoutText)(done) : undefined;
|
|
41
|
+
};
|
|
42
|
+
exports.git = git;
|
|
43
|
+
const realPath = async (target) => {
|
|
44
|
+
try {
|
|
45
|
+
return await (0, promises_1.realpath)(target);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return target;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const commonDirectory = async (cwd, repo) => {
|
|
52
|
+
const out = await (0, exports.git)(cwd, repo, ['rev-parse', '--git-common-dir']);
|
|
53
|
+
return out === undefined ? undefined : realPath(node_path_1.default.resolve(cwd, repo, out.trim()));
|
|
54
|
+
};
|
|
55
|
+
exports.commonDirectory = commonDirectory;
|
|
56
|
+
const batchEntry = (batch, file, position) => {
|
|
57
|
+
const end = batch.out.indexOf(0x0A, position);
|
|
58
|
+
const header = batch.out.subarray(position, end).toString('utf8')
|
|
59
|
+
.split(' ');
|
|
60
|
+
if (header.at(-1) === 'missing') {
|
|
61
|
+
throw new config_js_1.ConfigError(`в ${batch.describe}:${file}`);
|
|
62
|
+
}
|
|
63
|
+
const size = Number(header[2]);
|
|
64
|
+
return [decoder.decode(batch.out.subarray(end + 1, end + 1 + size)), end + 1 + size + 1];
|
|
65
|
+
};
|
|
66
|
+
const parseBatch = (batch) => {
|
|
67
|
+
const texts = new Map();
|
|
68
|
+
let position = 0;
|
|
69
|
+
for (const file of batch.files) {
|
|
70
|
+
const [text, next] = batchEntry(batch, file, position);
|
|
71
|
+
texts.set(file, text);
|
|
72
|
+
position = next;
|
|
73
|
+
}
|
|
74
|
+
return texts;
|
|
75
|
+
};
|
|
76
|
+
const readAtRevision = async (location, revision, files) => {
|
|
77
|
+
const done = await (0, exports.runGit)(location, ['cat-file', '--batch'], files.map((file) => `${revision}:${file}\n`).join(''));
|
|
78
|
+
if (done.code !== 0) {
|
|
79
|
+
throw new config_js_1.ConfigError(`git cat-file в ${location.repo} на ${revision} не прошёл: ${done.stderr.trim()}`);
|
|
80
|
+
}
|
|
81
|
+
return parseBatch({ describe: `${location.repo} нет ${revision}`, files, out: done.stdout });
|
|
82
|
+
};
|
|
83
|
+
exports.readAtRevision = readAtRevision;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export * from './acceptance-class.js';
|
|
2
|
+
export * from './board.js';
|
|
3
|
+
export * from './classes.js';
|
|
4
|
+
export * from './classify.js';
|
|
5
|
+
export * from './config.js';
|
|
6
|
+
export * from './coverage.js';
|
|
7
|
+
export * from './coverage-data.js';
|
|
8
|
+
export * from './coverage-json.js';
|
|
9
|
+
export * from './coverage-open.js';
|
|
10
|
+
export * from './coverage-page.js';
|
|
11
|
+
export * from './coverage-removed.js';
|
|
12
|
+
export * from './dates.js';
|
|
13
|
+
export * from './declared.js';
|
|
14
|
+
export * from './features.js';
|
|
15
|
+
export * from './gherkin.js';
|
|
16
|
+
export * from './git.js';
|
|
17
|
+
export * from './inventory.js';
|
|
18
|
+
export * from './links.js';
|
|
19
|
+
export * from './lint.js';
|
|
20
|
+
export * from './lint-inputs.js';
|
|
21
|
+
export * from './projects.js';
|
|
22
|
+
export * from './python-text.js';
|
|
23
|
+
export * from './remote.js';
|
|
24
|
+
export * from './removed-list.js';
|
|
25
|
+
export * from './report.js';
|
|
26
|
+
export * from './run.js';
|
|
27
|
+
export * from './task-links.js';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./acceptance-class.js"), exports);
|
|
18
|
+
__exportStar(require("./board.js"), exports);
|
|
19
|
+
__exportStar(require("./classes.js"), exports);
|
|
20
|
+
__exportStar(require("./classify.js"), exports);
|
|
21
|
+
__exportStar(require("./config.js"), exports);
|
|
22
|
+
__exportStar(require("./coverage.js"), exports);
|
|
23
|
+
__exportStar(require("./coverage-data.js"), exports);
|
|
24
|
+
__exportStar(require("./coverage-json.js"), exports);
|
|
25
|
+
__exportStar(require("./coverage-open.js"), exports);
|
|
26
|
+
__exportStar(require("./coverage-page.js"), exports);
|
|
27
|
+
__exportStar(require("./coverage-removed.js"), exports);
|
|
28
|
+
__exportStar(require("./dates.js"), exports);
|
|
29
|
+
__exportStar(require("./declared.js"), exports);
|
|
30
|
+
__exportStar(require("./features.js"), exports);
|
|
31
|
+
__exportStar(require("./gherkin.js"), exports);
|
|
32
|
+
__exportStar(require("./git.js"), exports);
|
|
33
|
+
__exportStar(require("./inventory.js"), exports);
|
|
34
|
+
__exportStar(require("./links.js"), exports);
|
|
35
|
+
__exportStar(require("./lint.js"), exports);
|
|
36
|
+
__exportStar(require("./lint-inputs.js"), exports);
|
|
37
|
+
__exportStar(require("./projects.js"), exports);
|
|
38
|
+
__exportStar(require("./python-text.js"), exports);
|
|
39
|
+
__exportStar(require("./remote.js"), exports);
|
|
40
|
+
__exportStar(require("./removed-list.js"), exports);
|
|
41
|
+
__exportStar(require("./report.js"), exports);
|
|
42
|
+
__exportStar(require("./run.js"), exports);
|
|
43
|
+
__exportStar(require("./task-links.js"), exports);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ConfigInterface } from './config.js';
|
|
2
|
+
import type { ReportInterface } from './report.js';
|
|
3
|
+
export declare const inventoryContract = 1;
|
|
4
|
+
export interface InventoryOptionsInterface {
|
|
5
|
+
readonly out: string;
|
|
6
|
+
readonly stories: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export declare const inventoryCommand: (cwd: string, config: ConfigInterface, options: InventoryOptionsInterface) => Promise<ReportInterface>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.inventoryCommand = exports.inventoryContract = void 0;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const acceptance_class_js_1 = require("./acceptance-class.js");
|
|
10
|
+
const config_js_1 = require("./config.js");
|
|
11
|
+
const features_js_1 = require("./features.js");
|
|
12
|
+
const gherkin_js_1 = require("./gherkin.js");
|
|
13
|
+
const python_text_js_1 = require("./python-text.js");
|
|
14
|
+
const removed_list_js_1 = require("./removed-list.js");
|
|
15
|
+
exports.inventoryContract = 1;
|
|
16
|
+
const fieldJson = (field) => ({
|
|
17
|
+
kinds: field.kinds,
|
|
18
|
+
line: field.line,
|
|
19
|
+
problem: field.problem ?? null,
|
|
20
|
+
reason: field.reason,
|
|
21
|
+
});
|
|
22
|
+
const storyFields = async (cwd, file) => {
|
|
23
|
+
try {
|
|
24
|
+
return { fields: (0, acceptance_class_js_1.acceptanceFields)(await (0, features_js_1.readText)(cwd, file)), file };
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw new config_js_1.ConfigError(`story ${file} не читается: ${(0, python_text_js_1.messageOf)(error)}`);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const filesJson = (files) => [...files]
|
|
31
|
+
.map(([file, text]) => ({ language: (0, gherkin_js_1.featureLanguage)(text), path: file, tags: (0, gherkin_js_1.featureTags)(text) }));
|
|
32
|
+
const inventoryCommand = async (cwd, config, options) => {
|
|
33
|
+
const files = await (0, features_js_1.worktreeFeatures)(cwd, config);
|
|
34
|
+
const scenarios = (0, features_js_1.allScenarios)(files);
|
|
35
|
+
const removed = await (0, removed_list_js_1.parseRemoved)(cwd, config.removed);
|
|
36
|
+
const stories = await Promise.all(options.stories.map(async (file) => storyFields(cwd, file)));
|
|
37
|
+
const inventory = {
|
|
38
|
+
config: { features: config.features, groups: config.groups, id: config.id.source, removed: config.removed },
|
|
39
|
+
contract: exports.inventoryContract,
|
|
40
|
+
files: filesJson(files),
|
|
41
|
+
removed: [...removed.entries].map(([id, line]) => ({ id, line })),
|
|
42
|
+
scenarios: scenarios.map((scenario) => ({ ...scenario, ids: scenario.tags.filter((tag) => config.id.test(tag)) })),
|
|
43
|
+
stories: stories.map((story) => ({ fields: story.fields.map((field) => fieldJson(field)), file: story.file })),
|
|
44
|
+
};
|
|
45
|
+
const target = node_path_1.default.resolve(cwd, options.out);
|
|
46
|
+
await (0, promises_1.mkdir)(node_path_1.default.dirname(target), { recursive: true });
|
|
47
|
+
await (0, promises_1.writeFile)(target, `${JSON.stringify(inventory, undefined, 2)}\n`);
|
|
48
|
+
return {
|
|
49
|
+
defects: [],
|
|
50
|
+
notes: [],
|
|
51
|
+
summary: `инвентарь: feature-файлов ${files.size}, сценариев ${scenarios.length}, снятых ${removed.entries.size}, `
|
|
52
|
+
+ `story ${stories.length} → ${options.out}`,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
exports.inventoryCommand = inventoryCommand;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ConfigInterface } from './config.js';
|
|
2
|
+
import type { ProjectsInputInterface } from './projects.js';
|
|
3
|
+
import type { ReportInterface } from './report.js';
|
|
4
|
+
export declare const linksCommand: (config: ConfigInterface, input: ProjectsInputInterface) => Promise<ReportInterface>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.linksCommand = void 0;
|
|
4
|
+
const config_js_1 = require("./config.js");
|
|
5
|
+
const features_js_1 = require("./features.js");
|
|
6
|
+
const gherkin_js_1 = require("./gherkin.js");
|
|
7
|
+
const python_text_js_1 = require("./python-text.js");
|
|
8
|
+
const removed_list_js_1 = require("./removed-list.js");
|
|
9
|
+
const task_links_js_1 = require("./task-links.js");
|
|
10
|
+
const wideFirst = new Set(['*', '\\', '<']);
|
|
11
|
+
const wideAfter = new Set(['.*', '.<']);
|
|
12
|
+
const templateStory = (tag) => {
|
|
13
|
+
const dot = tag.lastIndexOf('.');
|
|
14
|
+
return dot === -1 ? tag : tag.slice(0, dot);
|
|
15
|
+
};
|
|
16
|
+
const storyWide = (config, link) => {
|
|
17
|
+
if (!config.id.test(link.tag)) {
|
|
18
|
+
return ['шаблон вместо номера', templateStory(link.tag)];
|
|
19
|
+
}
|
|
20
|
+
const isWhole = wideFirst.has(link.after.slice(0, 1)) || wideAfter.has(link.after);
|
|
21
|
+
return isWhole ? ['story целиком', link.tag] : undefined;
|
|
22
|
+
};
|
|
23
|
+
const problemOutcome = (link, problem) => (link.isClosed
|
|
24
|
+
? { defect: `ссылка из закрытой story никуда не ведёт (${problem}): @${link.tag} — ${link.location}` }
|
|
25
|
+
: { todo: `завести: @${link.tag} — ${link.location} (${problem}, story ${link.status || 'вне board'})` });
|
|
26
|
+
const liveOutcome = (link, owner) => {
|
|
27
|
+
if (owner.tags.includes(config_js_1.markTodo)) {
|
|
28
|
+
return { debt: link };
|
|
29
|
+
}
|
|
30
|
+
const isPendingClosed = link.isClosed && owner.tags.includes(config_js_1.markPending);
|
|
31
|
+
const defect = `закрытая story ссылается на сценарий «ждёт реализации» @${link.tag}: ${link.location} → ${(0, gherkin_js_1.where)(owner)}`;
|
|
32
|
+
return isPendingClosed ? { defect } : {};
|
|
33
|
+
};
|
|
34
|
+
const wideOutcome = (known, link, wide) => {
|
|
35
|
+
const [kind, story] = wide;
|
|
36
|
+
const hasScenarios = [...known.ids.keys(), ...known.removed.keys()].some((id) => id.startsWith(`${story}.`));
|
|
37
|
+
return hasScenarios ? {} : problemOutcome(link, `${kind}, а сценариев этой story нет`);
|
|
38
|
+
};
|
|
39
|
+
const exactOutcome = (known, link) => {
|
|
40
|
+
const owner = known.ids.get(link.tag)?.[0];
|
|
41
|
+
if (owner !== undefined) {
|
|
42
|
+
return liveOutcome(link, owner);
|
|
43
|
+
}
|
|
44
|
+
return known.removed.has(link.tag) ? {} : problemOutcome(link, 'сценария нет');
|
|
45
|
+
};
|
|
46
|
+
const outcomeOf = (config, known, link) => {
|
|
47
|
+
const wide = storyWide(config, link);
|
|
48
|
+
return wide === undefined ? exactOutcome(known, link) : wideOutcome(known, link, wide);
|
|
49
|
+
};
|
|
50
|
+
const linkOf = (row, match) => {
|
|
51
|
+
const end = match.index + match[0].length;
|
|
52
|
+
return {
|
|
53
|
+
after: row.text.slice(end, end + 2),
|
|
54
|
+
isClosed: row.status === 'done',
|
|
55
|
+
location: `${row.project}/${row.file}:${row.line}`,
|
|
56
|
+
status: row.status,
|
|
57
|
+
tag: match[0].slice(1),
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
const rowLinks = (config, row) => [...row.text.matchAll(task_links_js_1.linkToken)]
|
|
61
|
+
.filter((match) => (0, config_js_1.isProjectCovered)(config, String(match[1])))
|
|
62
|
+
.map((match) => linkOf(row, match));
|
|
63
|
+
const summaryOf = (scenarios, counts) => {
|
|
64
|
+
const pending = (0, features_js_1.countMark)(scenarios, config_js_1.markPending);
|
|
65
|
+
const quarantine = (0, features_js_1.countMark)(scenarios, config_js_1.markQuarantine);
|
|
66
|
+
const todo = (0, features_js_1.countMark)(scenarios, config_js_1.markTodo);
|
|
67
|
+
const tested = `с тестом ${scenarios.length - todo}, из них @${config_js_1.markPending} ${pending}, @${config_js_1.markQuarantine} ${quarantine}`;
|
|
68
|
+
const links = `ссылок из закрытых story ${counts.closed}, из незакрытых ${counts.open}`;
|
|
69
|
+
return `прочитано: сценариев ${scenarios.length} (${tested}; долгов @${config_js_1.markTodo} ${todo}), снятых ${counts.removed}; ${links}`;
|
|
70
|
+
};
|
|
71
|
+
const debtLine = (id, owner, references) => `долг: @${id} — ${(0, gherkin_js_1.where)(owner)}; ссылки: ${references.join(', ') || 'нет'}`;
|
|
72
|
+
const debtLines = (known, outcomes) => [...known.ids]
|
|
73
|
+
.toSorted(([left], [right]) => (0, python_text_js_1.compareText)(left, right))
|
|
74
|
+
.flatMap(([id, owners]) => owners.slice(0, 1)
|
|
75
|
+
.filter((owner) => owner.tags.includes(config_js_1.markTodo))
|
|
76
|
+
.map((owner) => debtLine(id, owner, outcomes.flatMap(({ debt }) => (debt?.tag === id ? [debt.location] : [])))));
|
|
77
|
+
const knownOf = async (config, input, ids) => {
|
|
78
|
+
const removed = await (0, removed_list_js_1.parseRemoved)(input.cwd, config.removed);
|
|
79
|
+
return { ids, removed: removed.entries };
|
|
80
|
+
};
|
|
81
|
+
const linksCommand = async (config, input) => {
|
|
82
|
+
const files = await (0, features_js_1.worktreeFeatures)(input.cwd, config);
|
|
83
|
+
const known = await knownOf(config, input, (0, features_js_1.scenarioIds)(config, files));
|
|
84
|
+
const read = await (0, task_links_js_1.readProjects)(input);
|
|
85
|
+
const links = read.rows.flatMap((row) => rowLinks(config, row));
|
|
86
|
+
const outcomes = links.map((link) => outcomeOf(config, known, link));
|
|
87
|
+
const closed = links.filter((link) => link.isClosed).length;
|
|
88
|
+
const summary = summaryOf((0, features_js_1.allScenarios)(files), { closed, open: links.length - closed, removed: known.removed.size });
|
|
89
|
+
return {
|
|
90
|
+
defects: outcomes.flatMap((outcome) => outcome.defect ?? []),
|
|
91
|
+
notes: read.notes,
|
|
92
|
+
summary: [
|
|
93
|
+
`${summary}\nзадачи: ${read.sources.join('; ') || 'нет'}`,
|
|
94
|
+
...debtLines(known, outcomes),
|
|
95
|
+
...outcomes.flatMap((outcome) => outcome.todo ?? []),
|
|
96
|
+
].join('\n'),
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
exports.linksCommand = linksCommand;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ConfigInterface } from './config.js';
|
|
2
|
+
export interface ListResultInterface {
|
|
3
|
+
readonly defects: readonly string[];
|
|
4
|
+
readonly notes: readonly string[];
|
|
5
|
+
readonly tests: number;
|
|
6
|
+
}
|
|
7
|
+
export interface UnusedResultInterface {
|
|
8
|
+
readonly count: number;
|
|
9
|
+
readonly defects: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
export declare const lintList: (config: ConfigInterface, cwd: string, file: string) => Promise<ListResultInterface>;
|
|
12
|
+
export declare const lintUnused: (cwd: string, file: string) => Promise<UnusedResultInterface>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.lintUnused = exports.lintList = void 0;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const config_js_1 = require("./config.js");
|
|
10
|
+
const python_text_js_1 = require("./python-text.js");
|
|
11
|
+
const unusedHeader = /Unused steps \((?<count>\d+)\)/u;
|
|
12
|
+
const unusedRow = /^[|│]\s*(?<pattern>.+?)\s*[|│]\s*(?<location>\S+:\d+)\s*[|│]\s*$/u;
|
|
13
|
+
const isJson = (value) => typeof value === 'object' && value !== null;
|
|
14
|
+
const asRecord = (value) => (isJson(value) ? value : {});
|
|
15
|
+
const asList = (value) => (Array.isArray(value) ? value : []);
|
|
16
|
+
const readInput = async (cwd, file, describe) => {
|
|
17
|
+
try {
|
|
18
|
+
return await (0, promises_1.readFile)(node_path_1.default.resolve(cwd, file), 'utf8');
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new config_js_1.ConfigError(`${describe} ${file} не читается: ${(0, python_text_js_1.messageOf)(error)}`);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const readJson = async (cwd, file) => {
|
|
25
|
+
const text = await readInput(cwd, file, 'список тестов');
|
|
26
|
+
try {
|
|
27
|
+
return asRecord(JSON.parse(text));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new config_js_1.ConfigError(`список тестов ${file} не читается: ${(0, python_text_js_1.messageOf)(error)}`);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const walkSpecs = (suite) => [
|
|
34
|
+
...asList(suite.specs).map((spec) => asRecord(spec)),
|
|
35
|
+
...asList(suite.suites).flatMap((child) => walkSpecs(asRecord(child))),
|
|
36
|
+
];
|
|
37
|
+
const errorText = (error) => {
|
|
38
|
+
const record = asRecord(error);
|
|
39
|
+
return (0, python_text_js_1.pyText)('message' in record ? record.message : error);
|
|
40
|
+
};
|
|
41
|
+
const specTags = (spec) => asList(spec.tags).map(String);
|
|
42
|
+
const markedTags = (spec) => {
|
|
43
|
+
const tags = new Set(specTags(spec));
|
|
44
|
+
return (0, python_text_js_1.sortedText)(config_js_1.marks.filter((mark) => tags.has(mark)));
|
|
45
|
+
};
|
|
46
|
+
const blockingDefects = (context, spec, project) => {
|
|
47
|
+
const marked = markedTags(spec).map((mark) => `@${mark}`)
|
|
48
|
+
.join(', ');
|
|
49
|
+
const isLeak = marked !== '' && context.blocking.includes(project);
|
|
50
|
+
return isLeak ? [`блокирующий проект «${project}» выбирает отмеченный сценарий (${marked}): ${(0, python_text_js_1.pyText)(spec.title)}`] : [];
|
|
51
|
+
};
|
|
52
|
+
const pendingDefects = (context, spec, project) => {
|
|
53
|
+
const isStray = project === context.pending && !specTags(spec).includes(config_js_1.markPending);
|
|
54
|
+
return isStray ? [`проект «${context.pending}» выбирает сценарий без @${config_js_1.markPending}: ${(0, python_text_js_1.pyText)(spec.title)}`] : [];
|
|
55
|
+
};
|
|
56
|
+
const testDefects = (context, spec, test) => {
|
|
57
|
+
const project = (0, python_text_js_1.pyText)(test.projectName);
|
|
58
|
+
return [...blockingDefects(context, spec, project), ...pendingDefects(context, spec, project)];
|
|
59
|
+
};
|
|
60
|
+
const projectDefects = (data, context) => {
|
|
61
|
+
const projects = asList(asRecord(data.config).projects).map((project) => (0, python_text_js_1.pyText)(asRecord(project).name));
|
|
62
|
+
const listed = projects.join(', ') || 'нет';
|
|
63
|
+
return [...context.blocking, context.pending]
|
|
64
|
+
.filter((name) => !projects.includes(name))
|
|
65
|
+
.map((name) => `нет проекта Playwright «${name}» (проекты: ${listed})`);
|
|
66
|
+
};
|
|
67
|
+
const contextOf = (config) => {
|
|
68
|
+
if (config.blocking.length === 0 || config.pending === undefined) {
|
|
69
|
+
throw new config_js_1.ConfigError('для --list-json в конфиге нужны «blocking» и «pending»');
|
|
70
|
+
}
|
|
71
|
+
return { blocking: config.blocking, pending: config.pending };
|
|
72
|
+
};
|
|
73
|
+
const specTests = (data) => asList(data.suites)
|
|
74
|
+
.flatMap((suite) => walkSpecs(asRecord(suite)))
|
|
75
|
+
.flatMap((spec) => asList(spec.tests).map((test) => [spec, asRecord(test)]));
|
|
76
|
+
const lintList = async (config, cwd, file) => {
|
|
77
|
+
const data = await readJson(cwd, file);
|
|
78
|
+
const errors = asList(data.errors).map((error) => `playwright --list: ${errorText(error)}`);
|
|
79
|
+
const context = contextOf(config);
|
|
80
|
+
const pairs = specTests(data);
|
|
81
|
+
const isMarked = pairs.some(([spec]) => markedTags(spec).length > 0);
|
|
82
|
+
return {
|
|
83
|
+
defects: [
|
|
84
|
+
...errors,
|
|
85
|
+
...projectDefects(data, context),
|
|
86
|
+
...pairs.flatMap(([spec, test]) => testDefects(context, spec, test)),
|
|
87
|
+
],
|
|
88
|
+
notes: isMarked ? [] : ['отмеченных сценариев в списке нет — исключение их блокирующим проектом не проверено'],
|
|
89
|
+
tests: pairs.length,
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
exports.lintList = lintList;
|
|
93
|
+
const unusedRowOf = (line) => {
|
|
94
|
+
const groups = unusedRow.exec(line)?.groups;
|
|
95
|
+
return groups === undefined ? [] : [[String(groups.pattern), String(groups.location)]];
|
|
96
|
+
};
|
|
97
|
+
const duplicateSteps = (rows) => (0, python_text_js_1.sortedText)(new Set(rows.map(([pattern]) => pattern)))
|
|
98
|
+
.map((pattern) => [pattern, rows.filter(([name]) => name === pattern).map(([, location]) => location)])
|
|
99
|
+
.filter(([, locations]) => locations.length > 1)
|
|
100
|
+
.map(([pattern, locations]) => `дубль шага «${pattern}»: ${locations.join(', ')}`);
|
|
101
|
+
const declaredCount = (text, file) => {
|
|
102
|
+
const groups = unusedHeader.exec(text)?.groups;
|
|
103
|
+
if (groups === undefined) {
|
|
104
|
+
throw new config_js_1.ConfigError(`в ${file} нет строки «Unused steps (N)» — формат вывода bddgen сменился`);
|
|
105
|
+
}
|
|
106
|
+
return Number(groups.count);
|
|
107
|
+
};
|
|
108
|
+
const lintUnused = async (cwd, file) => {
|
|
109
|
+
const text = await readInput(cwd, file, 'вывод неиспользуемых шагов');
|
|
110
|
+
const declared = declaredCount(text, file);
|
|
111
|
+
const rows = (0, python_text_js_1.splitLines)(text).flatMap((line) => unusedRowOf(line));
|
|
112
|
+
if (rows.length !== declared) {
|
|
113
|
+
throw new config_js_1.ConfigError(`bddgen объявил ${declared} неиспользуемых шагов, разобрано ${rows.length} — формат вывода сменился`);
|
|
114
|
+
}
|
|
115
|
+
const unused = rows.map(([pattern, location]) => `неиспользуемый шаг «${pattern}»: ${location}`);
|
|
116
|
+
return { count: rows.length, defects: [...unused, ...duplicateSteps(rows)] };
|
|
117
|
+
};
|
|
118
|
+
exports.lintUnused = lintUnused;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ConfigInterface } from './config.js';
|
|
2
|
+
import type { ReportInterface } from './report.js';
|
|
3
|
+
export interface LintOptionsInterface {
|
|
4
|
+
readonly listJson: string | undefined;
|
|
5
|
+
readonly unusedSteps: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export declare const lintCommand: (cwd: string, config: ConfigInterface, options: LintOptionsInterface) => Promise<ReportInterface>;
|
|
8
|
+
export declare const removedCommand: (cwd: string, config: ConfigInterface, base: string) => Promise<ReportInterface>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removedCommand = exports.lintCommand = void 0;
|
|
4
|
+
const config_js_1 = require("./config.js");
|
|
5
|
+
const features_js_1 = require("./features.js");
|
|
6
|
+
const gherkin_js_1 = require("./gherkin.js");
|
|
7
|
+
const lint_inputs_js_1 = require("./lint-inputs.js");
|
|
8
|
+
const python_text_js_1 = require("./python-text.js");
|
|
9
|
+
const removed_list_js_1 = require("./removed-list.js");
|
|
10
|
+
const noPart = { defects: [], notes: [], summary: '' };
|
|
11
|
+
const isGiven = (file) => file !== undefined && file !== '';
|
|
12
|
+
const idDefects = (config, scenario) => {
|
|
13
|
+
const ids = scenario.tags.filter((tag) => config.id.test(tag));
|
|
14
|
+
if (ids.length === 0) {
|
|
15
|
+
return [`без id критерия: ${(0, gherkin_js_1.where)(scenario)}`];
|
|
16
|
+
}
|
|
17
|
+
return ids.length > 1 ? [`несколько id (${ids.join(', ')}): ${(0, gherkin_js_1.where)(scenario)}`] : [];
|
|
18
|
+
};
|
|
19
|
+
const groupDefects = (config, scenario) => {
|
|
20
|
+
const hasGroup = scenario.tags.some((tag) => config.groups.includes(tag));
|
|
21
|
+
return hasGroup ? [] : [`без тега группы (${config.groups.join(', ')}): ${(0, gherkin_js_1.where)(scenario)}`];
|
|
22
|
+
};
|
|
23
|
+
const todoDefects = (scenario) => {
|
|
24
|
+
const others = (0, python_text_js_1.sortedText)(new Set(scenario.tags.filter((tag) => tag === config_js_1.markPending || tag === config_js_1.markQuarantine)));
|
|
25
|
+
const listed = others.map((mark) => `@${mark}`).join(', ');
|
|
26
|
+
const isMixed = others.length > 0 && scenario.tags.includes(config_js_1.markTodo);
|
|
27
|
+
return isMixed ? [`@${config_js_1.markTodo} вместе с ${listed} — теста ещё нет, отметка состояния теста ему не нужна: ${(0, gherkin_js_1.where)(scenario)}`] : [];
|
|
28
|
+
};
|
|
29
|
+
const scenarioDefects = (config, scenario) => [
|
|
30
|
+
...idDefects(config, scenario),
|
|
31
|
+
...groupDefects(config, scenario),
|
|
32
|
+
...todoDefects(scenario),
|
|
33
|
+
];
|
|
34
|
+
const duplicateIds = (ids) => (0, python_text_js_1.sortedText)(ids.keys())
|
|
35
|
+
.map((tag) => [tag, ids.get(tag) ?? []])
|
|
36
|
+
.filter(([, owners]) => owners.length > 1)
|
|
37
|
+
.map(([tag, owners]) => `id ${tag} у ${owners.length} сценариев: ${owners.map((owner) => (0, gherkin_js_1.where)(owner)).join('; ')}`);
|
|
38
|
+
const listPart = async (config, cwd, file) => {
|
|
39
|
+
if (!isGiven(file)) {
|
|
40
|
+
return noPart;
|
|
41
|
+
}
|
|
42
|
+
const result = await (0, lint_inputs_js_1.lintList)(config, cwd, file);
|
|
43
|
+
return { ...result, summary: `, тестов в списке Playwright ${result.tests}` };
|
|
44
|
+
};
|
|
45
|
+
const unusedPart = async (cwd, file) => {
|
|
46
|
+
if (!isGiven(file)) {
|
|
47
|
+
return noPart;
|
|
48
|
+
}
|
|
49
|
+
const result = await (0, lint_inputs_js_1.lintUnused)(cwd, file);
|
|
50
|
+
return { defects: result.defects, notes: [], summary: `, неиспользуемых шагов ${result.count}` };
|
|
51
|
+
};
|
|
52
|
+
const lintCommand = async (cwd, config, options) => {
|
|
53
|
+
const files = await (0, features_js_1.worktreeFeatures)(cwd, config);
|
|
54
|
+
const scenarios = (0, features_js_1.allScenarios)(files);
|
|
55
|
+
const list = await listPart(config, cwd, options.listJson);
|
|
56
|
+
const unused = await unusedPart(cwd, options.unusedSteps);
|
|
57
|
+
const counts = config_js_1.marks.map((mark) => `@${mark} ${(0, features_js_1.countMark)(scenarios, mark)}`).join(', ');
|
|
58
|
+
const read = `прочитано: feature-файлов ${files.size}, сценариев ${scenarios.length}`;
|
|
59
|
+
return {
|
|
60
|
+
defects: [
|
|
61
|
+
...scenarios.flatMap((scenario) => scenarioDefects(config, scenario)),
|
|
62
|
+
...duplicateIds((0, features_js_1.scenarioIds)(config, files)),
|
|
63
|
+
...list.defects,
|
|
64
|
+
...unused.defects,
|
|
65
|
+
],
|
|
66
|
+
notes: list.notes,
|
|
67
|
+
summary: `${read}${list.summary}${unused.summary}\nотметки: ${counts}`,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
exports.lintCommand = lintCommand;
|
|
71
|
+
const reusedDefect = (config, id, line, owner) => {
|
|
72
|
+
const place = (0, gherkin_js_1.where)(owner);
|
|
73
|
+
return `id ${id} снят (${config.removed}:${line}), но снова в ${place} — id не переиспользуются и не переименовываются`;
|
|
74
|
+
};
|
|
75
|
+
const removedCommand = async (cwd, config, base) => {
|
|
76
|
+
const before = (0, features_js_1.scenarioIds)(config, await (0, features_js_1.baseFeatures)(cwd, config, base));
|
|
77
|
+
const now = (0, features_js_1.scenarioIds)(config, await (0, features_js_1.worktreeFeatures)(cwd, config));
|
|
78
|
+
const removed = await (0, removed_list_js_1.parseRemoved)(cwd, config.removed);
|
|
79
|
+
const gone = (0, python_text_js_1.sortedText)(before.keys()).filter((id) => !now.has(id) && !removed.entries.has(id));
|
|
80
|
+
const reused = [...removed.entries]
|
|
81
|
+
.toSorted(([left], [right]) => (0, python_text_js_1.compareText)(left, right))
|
|
82
|
+
.flatMap(([id, line]) => (now.get(id) ?? []).slice(0, 1).map((owner) => reusedDefect(config, id, line, owner)));
|
|
83
|
+
const read = `прочитано: id на базе ${base} ${before.size}, id на ветке ${now.size}`;
|
|
84
|
+
return {
|
|
85
|
+
defects: [
|
|
86
|
+
...removed.defects,
|
|
87
|
+
...gone.map((id) => `id ${id} исчез из feature-файлов без записи в ${config.removed}`),
|
|
88
|
+
...reused,
|
|
89
|
+
],
|
|
90
|
+
notes: [],
|
|
91
|
+
summary: `${read}, записей в ${config.removed} ${removed.entries.size}`,
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
exports.removedCommand = removedCommand;
|