@dungarees/bin-audit-comments-domain 0.11.6

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/behavior.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { AuditCommentsEvent } from './events.ts';
2
+ import type { CliCommands } from '@dungarees/cli-command/service.ts';
3
+ import { type Observable } from 'rxjs';
4
+ export type AuditCommentsFeatureOutput = {
5
+ events$: Observable<AuditCommentsEvent>;
6
+ };
7
+ export type AuditCommentsBehavior = {
8
+ audit: (args: {
9
+ ref: string;
10
+ dir: string;
11
+ }) => AuditCommentsFeatureOutput;
12
+ };
13
+ export type CreateAuditCommentsBehaviorOptions = {
14
+ cliCommands: CliCommands;
15
+ };
16
+ export declare const createAuditCommentsBehavior: ({ cliCommands: { git }, }: CreateAuditCommentsBehaviorOptions) => AuditCommentsBehavior;
package/behavior.js ADDED
@@ -0,0 +1,14 @@
1
+ import { getAddedComments, getAuditStartEvent, reportAddedComments } from './operations.js';
2
+ import { concat } from 'rxjs';
3
+ export const createAuditCommentsBehavior = ({ cliCommands: { git }, }) => {
4
+ const audit = ({ ref, dir }) => {
5
+ const startEvent$ = getAuditStartEvent({ ref });
6
+ const audit$ = getAddedComments({
7
+ getDiff: () => git.diff({ ref, cwd: dir }).output$,
8
+ }).pipe(reportAddedComments());
9
+ return {
10
+ events$: concat(startEvent$, audit$),
11
+ };
12
+ };
13
+ return { audit };
14
+ };
package/events.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { type DomainEventOf } from '@dungarees/core/event.ts';
2
+ export type AddedComment = {
3
+ file: string;
4
+ line: number;
5
+ comment: string;
6
+ context: string[];
7
+ };
8
+ type AuditCommentsEventPayloads = {
9
+ 'comment-audit-start': {
10
+ ref: string;
11
+ };
12
+ 'comment-added': AddedComment;
13
+ 'no-comments-added': undefined;
14
+ 'comments-need-review': {
15
+ count: number;
16
+ };
17
+ };
18
+ export type AuditCommentsEvent = DomainEventOf<AuditCommentsEventPayloads>;
19
+ export declare const eventCreators: any;
20
+ export {};
package/events.js ADDED
@@ -0,0 +1,2 @@
1
+ import { createEventCreators } from '@dungarees/core/event.ts';
2
+ export const eventCreators = createEventCreators();
@@ -0,0 +1 @@
1
+ export declare const isJudgedFile: (file: string) => boolean;
@@ -0,0 +1,4 @@
1
+ // Only languages whose comments this can recognise without guessing: a `//` in markdown or YAML is
2
+ // prose or a value, not a comment.
3
+ const JUDGED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
4
+ export const isJudgedFile = (file) => JUDGED_EXTENSIONS.some((extension) => file.endsWith(extension));
@@ -0,0 +1,12 @@
1
+ import { type AddedComment, type AuditCommentsEvent } from './events.ts';
2
+ import { type Observable, type OperatorFunction } from 'rxjs';
3
+ export declare const parseAddedComments: (diff: string) => AddedComment[];
4
+ export declare const reportAddedComments: () => OperatorFunction<AddedComment[], AuditCommentsEvent>;
5
+ export declare const getAuditStartEvent: ({ ref }: {
6
+ ref: string;
7
+ }) => Observable<AuditCommentsEvent>;
8
+ export declare const getAddedComments: ({ getDiff, }: {
9
+ getDiff: () => Observable<{
10
+ stdout: string;
11
+ }>;
12
+ }) => Observable<AddedComment[]>;
package/operations.js ADDED
@@ -0,0 +1,72 @@
1
+ import { eventCreators } from './events.js';
2
+ import { isJudgedFile } from './judged-files.js';
3
+ import { concat, from, of } from 'rxjs';
4
+ import { map, mergeMap } from 'rxjs/operators';
5
+ const isComment = (line) => /^(\/\/|\/\*|\*)/.test(line.trim());
6
+ const CONTEXT_LINES = 2;
7
+ const FILE_HEADER = /^\+\+\+ b\/(.*)$/;
8
+ const HUNK_HEADER = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
9
+ // A removal is absent from the new file and a header occupies no line of it, so only these two
10
+ // advance the line number.
11
+ const isNewFileLine = (raw) => raw.startsWith('+') || raw.startsWith(' ');
12
+ // A unified diff carries the new-file line number only in its hunk headers, so the numbers have to
13
+ // be counted forward from there.
14
+ const scanDiffLine = (scan, raw) => {
15
+ const file = FILE_HEADER.exec(raw)?.[1];
16
+ if (file !== undefined) {
17
+ return { ...scan, file };
18
+ }
19
+ const hunkStart = HUNK_HEADER.exec(raw)?.[1];
20
+ if (hunkStart !== undefined) {
21
+ return { ...scan, line: Number(hunkStart) };
22
+ }
23
+ if (!isNewFileLine(raw)) {
24
+ return scan;
25
+ }
26
+ return {
27
+ ...scan,
28
+ line: scan.line + 1,
29
+ lines: [
30
+ ...scan.lines,
31
+ { file: scan.file, line: scan.line, text: raw.slice(1), added: raw.startsWith('+') },
32
+ ],
33
+ };
34
+ };
35
+ const toDiffLines = (diff) => diff.split('\n').reduce(scanDiffLine, { file: '', line: 0, lines: [] }).lines;
36
+ const continuesBlock = (entry, first) => entry !== undefined && entry.file === first.file && entry.added && isComment(entry.text);
37
+ const isBlockStart = (lines, index, first) => !continuesBlock(lines[index - 1], first);
38
+ const takeBlock = (lines, index, first) => {
39
+ const fromBlockStart = lines.slice(index);
40
+ const end = fromBlockStart.findIndex((entry) => !continuesBlock(entry, first));
41
+ return end === -1 ? fromBlockStart : fromBlockStart.slice(0, end);
42
+ };
43
+ const getContext = (lines, first) => lines
44
+ .filter((next) => next.file === first.file)
45
+ .filter((next) => next.text.trim() !== '' && !isComment(next.text))
46
+ .slice(0, CONTEXT_LINES)
47
+ .map((next) => next.text.trim());
48
+ export const parseAddedComments = (diff) => {
49
+ const lines = toDiffLines(diff);
50
+ return lines.flatMap((entry, index) => {
51
+ if (!entry.added || !isJudgedFile(entry.file) || !isComment(entry.text)) {
52
+ return [];
53
+ }
54
+ if (!isBlockStart(lines, index, entry)) {
55
+ return [];
56
+ }
57
+ const block = takeBlock(lines, index, entry);
58
+ return [
59
+ {
60
+ file: entry.file,
61
+ line: entry.line,
62
+ comment: block.map(({ text }) => text.trim()).join('\n'),
63
+ context: getContext(lines.slice(index + block.length), entry),
64
+ },
65
+ ];
66
+ });
67
+ };
68
+ export const reportAddedComments = () => mergeMap((comments) => comments.length === 0
69
+ ? of(eventCreators.noCommentsAdded())
70
+ : concat(from(comments).pipe(map((comment) => eventCreators.commentAdded(comment))), of(eventCreators.commentsNeedReview({ count: comments.length }))));
71
+ export const getAuditStartEvent = ({ ref }) => of(eventCreators.commentAuditStart({ ref }));
72
+ export const getAddedComments = ({ getDiff, }) => getDiff().pipe(map(({ stdout }) => parseAddedComments(stdout)));
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@dungarees/bin-audit-comments-domain",
3
+ "engines": {
4
+ "node": ">=25.0.0"
5
+ },
6
+ "scripts": {
7
+ "type-check": "tsc --noEmit"
8
+ },
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@dungarees/cli-command": "*",
12
+ "@dungarees/core": "*",
13
+ "rxjs": "^7.8.1",
14
+ "typescript": "^5.9.3"
15
+ },
16
+ "devDependencies": {
17
+ "@dungarees/rxjs": "*",
18
+ "@dungarees/sub-process": "*",
19
+ "vitest": "^3.0.2"
20
+ },
21
+ "author": "info@productkind.com",
22
+ "license": "MIT",
23
+ "version": "0.11.6",
24
+ "exports": {
25
+ "./behavior.ts": {
26
+ "import": "./behavior.js",
27
+ "types": "./behavior.d.ts"
28
+ },
29
+ "./events.ts": {
30
+ "import": "./events.js",
31
+ "types": "./events.d.ts"
32
+ },
33
+ "./judged-files.ts": {
34
+ "import": "./judged-files.js",
35
+ "types": "./judged-files.d.ts"
36
+ },
37
+ "./operations.ts": {
38
+ "import": "./operations.js",
39
+ "types": "./operations.d.ts"
40
+ }
41
+ }
42
+ }