@design.estate/wcctools 5.1.0 → 6.1.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/.smartconfig.json +7 -3
- package/changelog.md +28 -0
- package/cli.js +3 -0
- package/dist_shell/bundle.js +2970 -0
- package/dist_shell/bundle.js.map +1 -0
- package/dist_shell/bundle.js.third-party-notices.json +1722 -0
- package/dist_shell/bundle.js.third-party-notices.txt +4195 -0
- package/dist_shell/index.html +21 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.devapi.d.ts +11 -0
- package/dist_ts/classes.devapi.js +21 -0
- package/dist_ts/classes.devserver.d.ts +49 -0
- package/dist_ts/classes.devserver.js +142 -0
- package/dist_ts/cli.d.ts +6 -0
- package/dist_ts/cli.js +51 -0
- package/dist_ts/index.d.ts +3 -0
- package/dist_ts/index.js +4 -0
- package/dist_ts/paths.d.ts +4 -0
- package/dist_ts/paths.js +6 -0
- package/dist_ts/plugins.d.ts +20 -0
- package/dist_ts/plugins.js +27 -0
- package/dist_ts/shellcontent.d.ts +9 -0
- package/dist_ts/shellcontent.js +42 -0
- package/dist_ts/standards/baselines.d.ts +27 -0
- package/dist_ts/standards/baselines.js +83 -0
- package/dist_ts/standards/checker.d.ts +21 -0
- package/dist_ts/standards/checker.js +88 -0
- package/dist_ts/standards/classes.gitrepo.d.ts +13 -0
- package/dist_ts/standards/classes.gitrepo.js +43 -0
- package/dist_ts/standards/classes.sourceedits.d.ts +15 -0
- package/dist_ts/standards/classes.sourceedits.js +54 -0
- package/dist_ts/standards/classes.standardfixer.d.ts +40 -0
- package/dist_ts/standards/classes.standardfixer.js +210 -0
- package/dist_ts/standards/classes.standardproject.d.ts +80 -0
- package/dist_ts/standards/classes.standardproject.js +195 -0
- package/dist_ts/standards/classes.standardtree.d.ts +56 -0
- package/dist_ts/standards/classes.standardtree.js +207 -0
- package/dist_ts/standards/cli.d.ts +7 -0
- package/dist_ts/standards/cli.js +89 -0
- package/dist_ts/standards/config.d.ts +33 -0
- package/dist_ts/standards/config.js +139 -0
- package/dist_ts/standards/errors.d.ts +9 -0
- package/dist_ts/standards/errors.js +17 -0
- package/dist_ts/standards/helpers.ast.d.ts +56 -0
- package/dist_ts/standards/helpers.ast.js +172 -0
- package/dist_ts/standards/helpers.paths.d.ts +36 -0
- package/dist_ts/standards/helpers.paths.js +86 -0
- package/dist_ts/standards/index.d.ts +14 -0
- package/dist_ts/standards/index.js +15 -0
- package/dist_ts/standards/output.d.ts +7 -0
- package/dist_ts/standards/output.js +57 -0
- package/dist_ts/standards/ratchet.d.ts +15 -0
- package/dist_ts/standards/ratchet.js +45 -0
- package/dist_ts/standards/rule.d.ts +50 -0
- package/dist_ts/standards/rule.js +9 -0
- package/dist_ts/standards/rules/barrel.element-index.d.ts +7 -0
- package/dist_ts/standards/rules/barrel.element-index.js +108 -0
- package/dist_ts/standards/rules/barrel.no-demo-export.d.ts +3 -0
- package/dist_ts/standards/rules/barrel.no-demo-export.js +29 -0
- package/dist_ts/standards/rules/demo.fixture-not-exported.d.ts +3 -0
- package/dist_ts/standards/rules/demo.fixture-not-exported.js +29 -0
- package/dist_ts/standards/rules/demo.groups.d.ts +6 -0
- package/dist_ts/standards/rules/demo.groups.js +72 -0
- package/dist_ts/standards/rules/demo.required.d.ts +3 -0
- package/dist_ts/standards/rules/demo.required.js +48 -0
- package/dist_ts/standards/rules/demo.separate-file.d.ts +3 -0
- package/dist_ts/standards/rules/demo.separate-file.js +38 -0
- package/dist_ts/standards/rules/demo.shape.d.ts +3 -0
- package/dist_ts/standards/rules/demo.shape.js +38 -0
- package/dist_ts/standards/rules/docs.component-index.d.ts +8 -0
- package/dist_ts/standards/rules/docs.component-index.js +84 -0
- package/dist_ts/standards/rules/element.exported.d.ts +3 -0
- package/dist_ts/standards/rules/element.exported.js +33 -0
- package/dist_ts/standards/rules/element.literal-tag.d.ts +3 -0
- package/dist_ts/standards/rules/element.literal-tag.js +30 -0
- package/dist_ts/standards/rules/element.reachable.d.ts +3 -0
- package/dist_ts/standards/rules/element.reachable.js +48 -0
- package/dist_ts/standards/rules/element.tag-map.d.ts +3 -0
- package/dist_ts/standards/rules/element.tag-map.js +83 -0
- package/dist_ts/standards/rules/i18n.literals.d.ts +3 -0
- package/dist_ts/standards/rules/i18n.literals.js +52 -0
- package/dist_ts/standards/rules/index.d.ts +12 -0
- package/dist_ts/standards/rules/index.js +83 -0
- package/dist_ts/standards/rules/layout.element-folder.d.ts +3 -0
- package/dist_ts/standards/rules/layout.element-folder.js +47 -0
- package/dist_ts/standards/rules/layout.entry-file-name.d.ts +3 -0
- package/dist_ts/standards/rules/layout.entry-file-name.js +71 -0
- package/dist_ts/standards/rules/layout.entry.d.ts +3 -0
- package/dist_ts/standards/rules/layout.entry.js +38 -0
- package/dist_ts/standards/rules/layout.group-folder.d.ts +3 -0
- package/dist_ts/standards/rules/layout.group-folder.js +22 -0
- package/dist_ts/standards/rules/layout.shared-00.d.ts +3 -0
- package/dist_ts/standards/rules/layout.shared-00.js +36 -0
- package/dist_ts/standards/rules/naming.class-matches-tag.d.ts +3 -0
- package/dist_ts/standards/rules/naming.class-matches-tag.js +26 -0
- package/dist_ts/standards/rules/naming.lowercase-files.d.ts +3 -0
- package/dist_ts/standards/rules/naming.lowercase-files.js +26 -0
- package/dist_ts/standards/rules/naming.tag-prefix.d.ts +3 -0
- package/dist_ts/standards/rules/naming.tag-prefix.js +23 -0
- package/dist_ts/standards/rules/props.accessor.d.ts +3 -0
- package/dist_ts/standards/rules/props.accessor.js +35 -0
- package/dist_ts/standards/rules/props.object-no-attribute.d.ts +3 -0
- package/dist_ts/standards/rules/props.object-no-attribute.js +42 -0
- package/dist_ts/standards/rules/props.reflect-primitive.d.ts +5 -0
- package/dist_ts/standards/rules/props.reflect-primitive.js +41 -0
- package/dist_ts/standards/rules/props.typed.d.ts +6 -0
- package/dist_ts/standards/rules/props.typed.js +64 -0
- package/dist_ts/standards/rules/shared.d.ts +38 -0
- package/dist_ts/standards/rules/shared.js +103 -0
- package/dist_ts/standards/rules/style.bdtheme.d.ts +5 -0
- package/dist_ts/standards/rules/style.bdtheme.js +28 -0
- package/dist_ts_interfaces/bridge.d.ts +88 -0
- package/dist_ts_interfaces/bridge.js +2 -0
- package/dist_ts_interfaces/catalog.d.ts +32 -0
- package/dist_ts_interfaces/catalog.js +6 -0
- package/dist_ts_interfaces/index.d.ts +4 -0
- package/dist_ts_interfaces/index.js +5 -0
- package/dist_ts_interfaces/plugins.d.ts +2 -0
- package/dist_ts_interfaces/plugins.js +3 -0
- package/dist_ts_interfaces/requests.d.ts +15 -0
- package/dist_ts_interfaces/requests.js +2 -0
- package/dist_ts_interfaces/standard.d.ts +93 -0
- package/dist_ts_interfaces/standard.js +6 -0
- package/dist_ts_shared/index.d.ts +1 -0
- package/dist_ts_shared/index.js +2 -0
- package/dist_ts_shared/plugins.d.ts +2 -0
- package/dist_ts_shared/plugins.js +3 -0
- package/dist_ts_shared/previewroute.d.ts +18 -0
- package/dist_ts_shared/previewroute.js +47 -0
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-contextmenu.js +1 -1
- package/dist_ts_shell/elements/wcc-preview-frame.d.ts +23 -0
- package/dist_ts_shell/elements/wcc-preview-frame.js +197 -0
- package/dist_ts_shell/elements/wcc-properties.d.ts +49 -0
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-properties.js +149 -216
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-record-button.js +1 -1
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-recording-panel.d.ts +2 -2
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-recording-panel.js +12 -16
- package/dist_ts_shell/elements/wcc-shell.d.ts +87 -0
- package/dist_ts_shell/elements/wcc-shell.js +524 -0
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-sidebar.d.ts +12 -10
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-sidebar.js +137 -183
- package/dist_ts_shell/index.d.ts +4 -0
- package/dist_ts_shell/index.js +5 -0
- package/dist_ts_shell/plugins.d.ts +4 -0
- package/dist_ts_shell/plugins.js +5 -0
- package/dist_ts_shell/previewconnection.d.ts +30 -0
- package/dist_ts_shell/previewconnection.js +86 -0
- package/dist_ts_shell/services/recorder.service.js +353 -0
- package/dist_ts_web/00_commitinfo_data.js +2 -2
- package/dist_ts_web/catalog.d.ts +45 -0
- package/dist_ts_web/catalog.js +153 -0
- package/dist_ts_web/elementproperties.d.ts +15 -0
- package/dist_ts_web/elementproperties.js +89 -0
- package/dist_ts_web/elements/wcc-preview.d.ts +19 -0
- package/dist_ts_web/elements/wcc-preview.js +164 -0
- package/dist_ts_web/elements/wcc-standalone-notice.d.ts +14 -0
- package/dist_ts_web/elements/wcc-standalone-notice.js +110 -0
- package/dist_ts_web/elements/wcctools.helpers.d.ts +2 -6
- package/dist_ts_web/elements/wcctools.helpers.js +1 -7
- package/dist_ts_web/index.d.ts +2 -5
- package/dist_ts_web/index.js +38 -17
- package/dist_ts_web/previewbridge.d.ts +39 -0
- package/dist_ts_web/previewbridge.js +153 -0
- package/dist_ts_web/renderedtree.d.ts +5 -0
- package/dist_ts_web/renderedtree.js +37 -0
- package/dist_ts_web/wcctools.interfaces.d.ts +3 -6
- package/dist_ts_web/wcctools.interfaces.js +1 -1
- package/dist_ts_web/wcctools.plugins.d.ts +3 -1
- package/dist_ts_web/wcctools.plugins.js +4 -2
- package/package.json +17 -7
- package/readme.md +93 -28
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.devapi.ts +25 -0
- package/ts/classes.devserver.ts +161 -0
- package/ts/cli.ts +53 -0
- package/ts/index.ts +3 -0
- package/ts/paths.ts +7 -0
- package/ts/plugins.ts +37 -0
- package/ts/shellcontent.ts +42 -0
- package/ts/standards/baselines.ts +100 -0
- package/ts/standards/checker.ts +114 -0
- package/ts/standards/classes.gitrepo.ts +48 -0
- package/ts/standards/classes.sourceedits.ts +58 -0
- package/ts/standards/classes.standardfixer.ts +234 -0
- package/ts/standards/classes.standardproject.ts +250 -0
- package/ts/standards/classes.standardtree.ts +220 -0
- package/ts/standards/cli.ts +86 -0
- package/ts/standards/config.ts +160 -0
- package/ts/standards/errors.ts +18 -0
- package/ts/standards/helpers.ast.ts +208 -0
- package/ts/standards/helpers.paths.ts +119 -0
- package/ts/standards/index.ts +22 -0
- package/ts/standards/output.ts +65 -0
- package/ts/standards/ratchet.ts +63 -0
- package/ts/standards/rule.ts +58 -0
- package/ts/standards/rules/barrel.element-index.ts +112 -0
- package/ts/standards/rules/barrel.no-demo-export.ts +30 -0
- package/ts/standards/rules/demo.fixture-not-exported.ts +30 -0
- package/ts/standards/rules/demo.groups.ts +74 -0
- package/ts/standards/rules/demo.required.ts +49 -0
- package/ts/standards/rules/demo.separate-file.ts +40 -0
- package/ts/standards/rules/demo.shape.ts +42 -0
- package/ts/standards/rules/docs.component-index.ts +88 -0
- package/ts/standards/rules/element.exported.ts +35 -0
- package/ts/standards/rules/element.literal-tag.ts +30 -0
- package/ts/standards/rules/element.reachable.ts +49 -0
- package/ts/standards/rules/element.tag-map.ts +87 -0
- package/ts/standards/rules/i18n.literals.ts +58 -0
- package/ts/standards/rules/index.ts +87 -0
- package/ts/standards/rules/layout.element-folder.ts +49 -0
- package/ts/standards/rules/layout.entry-file-name.ts +73 -0
- package/ts/standards/rules/layout.entry.ts +40 -0
- package/ts/standards/rules/layout.group-folder.ts +23 -0
- package/ts/standards/rules/layout.shared-00.ts +37 -0
- package/ts/standards/rules/naming.class-matches-tag.ts +27 -0
- package/ts/standards/rules/naming.lowercase-files.ts +27 -0
- package/ts/standards/rules/naming.tag-prefix.ts +24 -0
- package/ts/standards/rules/props.accessor.ts +37 -0
- package/ts/standards/rules/props.object-no-attribute.ts +45 -0
- package/ts/standards/rules/props.reflect-primitive.ts +44 -0
- package/ts/standards/rules/props.typed.ts +70 -0
- package/ts/standards/rules/shared.ts +127 -0
- package/ts/standards/rules/style.bdtheme.ts +31 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/catalog.ts +194 -0
- package/ts_web/elementproperties.ts +96 -0
- package/ts_web/elements/wcc-preview.ts +111 -0
- package/ts_web/elements/wcc-standalone-notice.ts +65 -0
- package/ts_web/elements/wcctools.helpers.ts +2 -10
- package/ts_web/index.ts +38 -19
- package/ts_web/previewbridge.ts +189 -0
- package/ts_web/readme.md +26 -85
- package/ts_web/renderedtree.ts +43 -0
- package/ts_web/wcctools.interfaces.ts +4 -7
- package/ts_web/wcctools.plugins.ts +6 -1
- package/dist_bundle/bundle.js +0 -92419
- package/dist_bundle/bundle.js.map +0 -1
- package/dist_ts_web/elements/wcc-dashboard.d.ts +0 -119
- package/dist_ts_web/elements/wcc-dashboard.js +0 -700
- package/dist_ts_web/elements/wcc-frame.d.ts +0 -22
- package/dist_ts_web/elements/wcc-frame.js +0 -256
- package/dist_ts_web/elements/wcc-properties.d.ts +0 -43
- package/dist_ts_web/pages/index.d.ts +0 -1
- package/dist_ts_web/pages/index.js +0 -2
- package/dist_ts_web/services/recorder.service.js +0 -353
- package/ts_web/elements/wcc-contextmenu.ts +0 -291
- package/ts_web/elements/wcc-dashboard.ts +0 -672
- package/ts_web/elements/wcc-frame.ts +0 -189
- package/ts_web/elements/wcc-properties.ts +0 -1089
- package/ts_web/elements/wcc-record-button.ts +0 -108
- package/ts_web/elements/wcc-recording-panel.ts +0 -1010
- package/ts_web/elements/wcc-sidebar.ts +0 -1470
- package/ts_web/pages/index.ts +0 -1
- package/ts_web/services/recorder.service.ts +0 -451
- package/ts_web/types/dom-mediacapture-stub/index.d.ts +0 -12
- package/ts_web/types/dom-mediacapture-stub/package.json +0 -6
- package/ts_web/types/dom-webcodecs-stub/index.d.ts +0 -2
- package/ts_web/types/dom-webcodecs-stub/package.json +0 -6
- /package/{dist_ts_web → dist_ts_shell}/elements/wcc-contextmenu.d.ts +0 -0
- /package/{dist_ts_web → dist_ts_shell}/elements/wcc-record-button.d.ts +0 -0
- /package/{dist_ts_web → dist_ts_shell}/services/recorder.service.d.ts +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import * as ast from './helpers.ast.js';
|
|
3
|
+
import { isDemoFile, isTypeScriptSource, resolveSpecifier, toPosix } from './helpers.paths.js';
|
|
4
|
+
|
|
5
|
+
const ts = plugins.typescript;
|
|
6
|
+
|
|
7
|
+
/** A class decorated with `@customElement(…)`. */
|
|
8
|
+
export interface IStandardElement {
|
|
9
|
+
file: string;
|
|
10
|
+
sourceFile: ast.TSourceFile;
|
|
11
|
+
classNode: ast.TClassDeclaration;
|
|
12
|
+
className: string | null;
|
|
13
|
+
decoratorCall: ast.TCallExpression;
|
|
14
|
+
/** The literal tag, or null when the decorator argument is not a string literal. */
|
|
15
|
+
tag: string | null;
|
|
16
|
+
exported: boolean;
|
|
17
|
+
inDemoFile: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A class member decorated with `@property(…)` or `@state(…)`. */
|
|
21
|
+
export interface IStandardProperty {
|
|
22
|
+
file: string;
|
|
23
|
+
sourceFile: ast.TSourceFile;
|
|
24
|
+
classNode: ast.TClassDeclaration;
|
|
25
|
+
member: ast.TClassElement;
|
|
26
|
+
name: string;
|
|
27
|
+
decorator: 'property' | 'state';
|
|
28
|
+
call: ast.TCallExpression;
|
|
29
|
+
/** The options object literal; null when the decorator has no argument or a non-literal one. */
|
|
30
|
+
options: ast.TObjectLiteralExpression | null;
|
|
31
|
+
/** True when the decorator has an argument that is not an object literal. */
|
|
32
|
+
dynamicOptions: boolean;
|
|
33
|
+
isPublic: boolean;
|
|
34
|
+
inDemoFile: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** An `export … from '<relative>'` declaration. */
|
|
38
|
+
export interface IStandardReexport {
|
|
39
|
+
file: string;
|
|
40
|
+
statement: plugins.typescript.ExportDeclaration;
|
|
41
|
+
specifier: string;
|
|
42
|
+
/** The resolved repository path, or null when it does not resolve to a known file. */
|
|
43
|
+
target: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type TParseCache = Map<string, { text: string; sourceFile: ast.TSourceFile }>;
|
|
47
|
+
|
|
48
|
+
/** The root barrel every reachability check starts from. */
|
|
49
|
+
export const rootBarrel = 'ts_web/index.ts';
|
|
50
|
+
export const elementsBarrel = 'ts_web/elements/index.ts';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The syntax-only model of a catalog the rules read: its files, elements, reactive properties and
|
|
54
|
+
* barrel graph. Built from a map of repository paths to file texts, so the same model serves a
|
|
55
|
+
* repository on disk and the fixer's in-memory tree.
|
|
56
|
+
*/
|
|
57
|
+
export class StandardProject {
|
|
58
|
+
public readonly files: ReadonlyMap<string, string>;
|
|
59
|
+
private readonly existsOnDisk: (fileArg: string) => boolean;
|
|
60
|
+
private readonly parseCache: TParseCache;
|
|
61
|
+
private elementsCache: IStandardElement[] | null = null;
|
|
62
|
+
private propertiesCache: IStandardProperty[] | null = null;
|
|
63
|
+
private reexportsCache = new Map<string, IStandardReexport[]>();
|
|
64
|
+
|
|
65
|
+
constructor(filesArg: ReadonlyMap<string, string>, existsOnDiskArg: (fileArg: string) => boolean, parseCacheArg: TParseCache = new Map()) {
|
|
66
|
+
this.files = filesArg;
|
|
67
|
+
this.existsOnDisk = existsOnDiskArg;
|
|
68
|
+
this.parseCache = parseCacheArg;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** True when the file is part of the model or exists in the repository. */
|
|
72
|
+
public exists(fileArg: string): boolean {
|
|
73
|
+
return this.files.has(fileArg) || this.existsOnDisk(fileArg);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The `ts_web/**\/*.ts` sources, sorted. */
|
|
77
|
+
public get webFiles(): string[] {
|
|
78
|
+
return [...this.files.keys()].filter((fileArg) => fileArg.startsWith('ts_web/') && fileArg.endsWith('.ts') && isTypeScriptSource(fileArg)).sort();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public sourceFile(fileArg: string): ast.TSourceFile {
|
|
82
|
+
const text = this.files.get(fileArg);
|
|
83
|
+
if (text === undefined) {
|
|
84
|
+
throw new Error(`Not a known file: ${fileArg}`);
|
|
85
|
+
}
|
|
86
|
+
const cached = this.parseCache.get(fileArg);
|
|
87
|
+
if (cached && cached.text === text) {
|
|
88
|
+
return cached.sourceFile;
|
|
89
|
+
}
|
|
90
|
+
const sourceFile = ast.parseSource(fileArg, text);
|
|
91
|
+
this.parseCache.set(fileArg, { text, sourceFile });
|
|
92
|
+
return sourceFile;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** `compilerOptions.experimentalDecorators` of tsconfig.json (its `extends` chain is not followed). */
|
|
96
|
+
public get experimentalDecorators(): boolean {
|
|
97
|
+
const text = this.files.get('tsconfig.json');
|
|
98
|
+
if (!text) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const parsed = ts.parseConfigFileTextToJson('tsconfig.json', text);
|
|
102
|
+
return parsed.config?.compilerOptions?.experimentalDecorators === true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public get elements(): IStandardElement[] {
|
|
106
|
+
if (!this.elementsCache) {
|
|
107
|
+
this.elementsCache = [];
|
|
108
|
+
for (const file of this.webFiles) {
|
|
109
|
+
const sourceFile = this.sourceFile(file);
|
|
110
|
+
const visit = (nodeArg: ast.TNode): void => {
|
|
111
|
+
if (ts.isClassDeclaration(nodeArg)) {
|
|
112
|
+
const decorator = ast.findDecoratorCall(nodeArg, ['customElement']);
|
|
113
|
+
if (decorator) {
|
|
114
|
+
const tagArgument = decorator.call.arguments[0];
|
|
115
|
+
this.elementsCache!.push({
|
|
116
|
+
file,
|
|
117
|
+
sourceFile,
|
|
118
|
+
classNode: nodeArg,
|
|
119
|
+
className: nodeArg.name?.text ?? null,
|
|
120
|
+
decoratorCall: decorator.call,
|
|
121
|
+
tag: decorator.call.arguments.length === 1 && ast.isStringLiteralLike(tagArgument) ? tagArgument.text : null,
|
|
122
|
+
exported: ast.hasModifier(nodeArg, ts.SyntaxKind.ExportKeyword),
|
|
123
|
+
inDemoFile: isDemoFile(file),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
ts.forEachChild(nodeArg, visit);
|
|
128
|
+
};
|
|
129
|
+
visit(sourceFile);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return this.elementsCache;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public get properties(): IStandardProperty[] {
|
|
136
|
+
if (!this.propertiesCache) {
|
|
137
|
+
this.propertiesCache = [];
|
|
138
|
+
for (const file of this.webFiles) {
|
|
139
|
+
const sourceFile = this.sourceFile(file);
|
|
140
|
+
const visit = (nodeArg: ast.TNode): void => {
|
|
141
|
+
if (ts.isClassDeclaration(nodeArg) || ts.isClassExpression(nodeArg)) {
|
|
142
|
+
for (const member of nodeArg.members) {
|
|
143
|
+
const decorator = ast.findDecoratorCall(member, ['property', 'state']);
|
|
144
|
+
const name = ast.memberNameText(member);
|
|
145
|
+
if (!decorator || name === null) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const firstArgument = decorator.call.arguments[0];
|
|
149
|
+
const isPrivateName = !!member.name && ts.isPrivateIdentifier(member.name);
|
|
150
|
+
this.propertiesCache!.push({
|
|
151
|
+
file,
|
|
152
|
+
sourceFile,
|
|
153
|
+
classNode: nodeArg as ast.TClassDeclaration,
|
|
154
|
+
member,
|
|
155
|
+
name: isPrivateName && !name.startsWith('#') ? `#${name}` : name,
|
|
156
|
+
decorator: decorator.name as 'property' | 'state',
|
|
157
|
+
call: decorator.call,
|
|
158
|
+
options: firstArgument && ts.isObjectLiteralExpression(firstArgument) ? firstArgument : null,
|
|
159
|
+
dynamicOptions: !!firstArgument && !ts.isObjectLiteralExpression(firstArgument),
|
|
160
|
+
isPublic: !isPrivateName
|
|
161
|
+
&& !ast.hasModifier(member, ts.SyntaxKind.PrivateKeyword)
|
|
162
|
+
&& !ast.hasModifier(member, ts.SyntaxKind.ProtectedKeyword),
|
|
163
|
+
inDemoFile: isDemoFile(file),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
ts.forEachChild(nodeArg, visit);
|
|
168
|
+
};
|
|
169
|
+
visit(sourceFile);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return this.propertiesCache;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The `export … from` declarations of a file with relative specifiers. */
|
|
176
|
+
public reexports(fileArg: string): IStandardReexport[] {
|
|
177
|
+
let reexports = this.reexportsCache.get(fileArg);
|
|
178
|
+
if (!reexports) {
|
|
179
|
+
reexports = [];
|
|
180
|
+
if (this.files.has(fileArg)) {
|
|
181
|
+
for (const statement of this.sourceFile(fileArg).statements) {
|
|
182
|
+
if (ts.isExportDeclaration(statement) && statement.moduleSpecifier && ast.isStringLiteralLike(statement.moduleSpecifier)) {
|
|
183
|
+
const specifier = statement.moduleSpecifier.text;
|
|
184
|
+
if (!specifier.startsWith('.')) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const resolved = resolveSpecifier(fileArg, specifier, (candidateArg) => this.files.has(candidateArg));
|
|
188
|
+
reexports.push({ file: fileArg, statement, specifier, target: resolved?.target ?? null });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
this.reexportsCache.set(fileArg, reexports);
|
|
193
|
+
}
|
|
194
|
+
return reexports;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Every file reached from a barrel by following `export … from` declarations. */
|
|
198
|
+
public reachableFrom(fileArg: string): Set<string> {
|
|
199
|
+
const reached = new Set<string>();
|
|
200
|
+
const queue = this.files.has(fileArg) ? [fileArg] : [];
|
|
201
|
+
while (queue.length > 0) {
|
|
202
|
+
const file = queue.shift()!;
|
|
203
|
+
if (reached.has(file)) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
reached.add(file);
|
|
207
|
+
for (const reexport of this.reexports(file)) {
|
|
208
|
+
if (reexport.target && !reached.has(reexport.target)) {
|
|
209
|
+
queue.push(reexport.target);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return reached;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public lineAndColumn(fileArg: string, positionArg: number): { line: number; column: number } {
|
|
217
|
+
return ast.lineAndColumn(this.sourceFile(fileArg), positionArg);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** The files besides `ts_web/**` a check reads. */
|
|
222
|
+
export const standardSideFiles = ['html/index.ts', 'html/index.html', 'tsconfig.json', 'readme.md'];
|
|
223
|
+
|
|
224
|
+
/** Reads the files the standard covers from a repository on disk. */
|
|
225
|
+
export const loadStandardProject = async (rootDirArg: string): Promise<StandardProject> => {
|
|
226
|
+
const files = new Map<string, string>();
|
|
227
|
+
const webDir = plugins.path.join(rootDirArg, 'ts_web');
|
|
228
|
+
if (plugins.fsSync.existsSync(webDir)) {
|
|
229
|
+
const entries = await plugins.fs.readdir(webDir, { recursive: true, withFileTypes: true });
|
|
230
|
+
for (const entry of entries) {
|
|
231
|
+
if (!entry.isFile()) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const relative = toPosix(plugins.path.relative(rootDirArg, plugins.path.join(entry.parentPath, entry.name)));
|
|
235
|
+
if (relative.split('/').some((segmentArg) => segmentArg === 'node_modules')) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (relative.endsWith('.ts') && isTypeScriptSource(relative)) {
|
|
239
|
+
files.set(relative, await plugins.fs.readFile(plugins.path.join(rootDirArg, relative), 'utf8'));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
for (const sideFile of standardSideFiles) {
|
|
244
|
+
const absolute = plugins.path.join(rootDirArg, sideFile);
|
|
245
|
+
if (plugins.fsSync.existsSync(absolute)) {
|
|
246
|
+
files.set(sideFile, await plugins.fs.readFile(absolute, 'utf8'));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return new StandardProject(files, (fileArg) => plugins.fsSync.existsSync(plugins.path.join(rootDirArg, fileArg)));
|
|
250
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import * as ast from './helpers.ast.js';
|
|
3
|
+
import { baseOf, isRepoPath, isTypeScriptSource, resolveSpecifier, specifierFor } from './helpers.paths.js';
|
|
4
|
+
import { SourceEdits } from './classes.sourceedits.js';
|
|
5
|
+
import { StandardProject, type TParseCache } from './classes.standardproject.js';
|
|
6
|
+
import type { IFixBuilder } from './rule.js';
|
|
7
|
+
|
|
8
|
+
const ts = plugins.typescript;
|
|
9
|
+
|
|
10
|
+
/** The changes one rule run collected. */
|
|
11
|
+
export class FixBatch implements IFixBuilder {
|
|
12
|
+
public readonly edits = new Map<string, Array<ast.ITextEdit>>();
|
|
13
|
+
public readonly creates = new Map<string, string>();
|
|
14
|
+
public readonly moves = new Map<string, string>();
|
|
15
|
+
public readonly redirects = new Map<string, string>();
|
|
16
|
+
|
|
17
|
+
public edit(fileArg: string, startArg: number, endArg: number, textArg: string): void {
|
|
18
|
+
this.edits.set(fileArg, [...(this.edits.get(fileArg) ?? []), { start: startArg, end: endArg, text: textArg }]);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public create(fileArg: string, textArg: string): void {
|
|
22
|
+
if (!this.creates.has(fileArg)) {
|
|
23
|
+
this.creates.set(fileArg, textArg);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public move(fromArg: string, toArg: string): void {
|
|
28
|
+
if (!this.moves.has(fromArg)) {
|
|
29
|
+
this.moves.set(fromArg, toArg);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public redirectBarrelExports(targetArg: string, barrelTargetArg: string): void {
|
|
34
|
+
this.redirects.set(targetArg, barrelTargetArg);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The fixer's in-memory copy of a repository: the tracked TypeScript sources and the side files the
|
|
40
|
+
* standard reads, with the moves and edits applied so far. Nothing touches the disk until the
|
|
41
|
+
* fixer commits the tree. Every path a batch names must be a repository path (see isRepoPath);
|
|
42
|
+
* a batch with any other path is rejected before it changes the tree.
|
|
43
|
+
*/
|
|
44
|
+
export class StandardTree {
|
|
45
|
+
public readonly files: Map<string, string>;
|
|
46
|
+
private readonly originalTexts = new Map<string, string>();
|
|
47
|
+
/** Current path → original path, for moved files. */
|
|
48
|
+
private readonly originOf = new Map<string, string>();
|
|
49
|
+
/** Original paths that were moved away. */
|
|
50
|
+
private readonly vacated = new Set<string>();
|
|
51
|
+
public readonly created = new Set<string>();
|
|
52
|
+
/** Current path → ids of the rules that changed the file. */
|
|
53
|
+
private readonly changedBy = new Map<string, Set<string>>();
|
|
54
|
+
private readonly parseCache: TParseCache = new Map();
|
|
55
|
+
/** Every tracked file of the repository, including those the tree holds no text for (`.json`, `.css`, `.d.ts`, …). */
|
|
56
|
+
private readonly trackedFiles: ReadonlySet<string>;
|
|
57
|
+
private readonly existsOnDisk: (fileArg: string) => boolean;
|
|
58
|
+
|
|
59
|
+
constructor(filesArg: Map<string, string>, trackedFilesArg: Iterable<string>, existsOnDiskArg: (fileArg: string) => boolean) {
|
|
60
|
+
this.files = new Map(filesArg);
|
|
61
|
+
for (const [file, text] of filesArg) {
|
|
62
|
+
this.originalTexts.set(file, text);
|
|
63
|
+
}
|
|
64
|
+
this.trackedFiles = new Set(trackedFilesArg);
|
|
65
|
+
this.existsOnDisk = existsOnDiskArg;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public exists(fileArg: string): boolean {
|
|
69
|
+
return this.files.has(fileArg) || (!this.vacated.has(fileArg) && this.existsOnDisk(fileArg));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The model of the tree's current state. */
|
|
73
|
+
public project(): StandardProject {
|
|
74
|
+
return new StandardProject(this.files, (fileArg) => this.exists(fileArg), this.parseCache);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private markChanged(fileArg: string, ruleIdArg: string): void {
|
|
78
|
+
this.changedBy.set(fileArg, new Set([...(this.changedBy.get(fileArg) ?? []), ruleIdArg]));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Applies a batch: edits, then new files, then moves with import rewriting. Returns the number of
|
|
83
|
+
* applied changes. Throws without changing the tree when the batch names a path that is not a
|
|
84
|
+
* repository path.
|
|
85
|
+
*/
|
|
86
|
+
public apply(ruleIdArg: string, batchArg: FixBatch): number {
|
|
87
|
+
const outside = [
|
|
88
|
+
...batchArg.edits.keys(),
|
|
89
|
+
...batchArg.creates.keys(),
|
|
90
|
+
...[...batchArg.moves].flat(),
|
|
91
|
+
...[...batchArg.redirects].flat(),
|
|
92
|
+
].filter((pathArg) => !isRepoPath(pathArg));
|
|
93
|
+
if (outside.length > 0) {
|
|
94
|
+
throw new Error(`The fix of ${ruleIdArg} names paths outside the repository root: ${[...new Set(outside)].join(', ')}`);
|
|
95
|
+
}
|
|
96
|
+
let applied = 0;
|
|
97
|
+
for (const [file, edits] of batchArg.edits) {
|
|
98
|
+
const text = this.files.get(file);
|
|
99
|
+
if (text === undefined) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const sourceEdits = new SourceEdits();
|
|
103
|
+
edits.forEach((editArg) => sourceEdits.add(editArg));
|
|
104
|
+
const next = sourceEdits.apply(text);
|
|
105
|
+
if (next !== text) {
|
|
106
|
+
this.files.set(file, next);
|
|
107
|
+
this.markChanged(file, ruleIdArg);
|
|
108
|
+
applied += sourceEdits.size;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const [file, text] of batchArg.creates) {
|
|
112
|
+
if (this.exists(file)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
this.files.set(file, text);
|
|
116
|
+
this.created.add(file);
|
|
117
|
+
this.markChanged(file, ruleIdArg);
|
|
118
|
+
applied++;
|
|
119
|
+
}
|
|
120
|
+
const moves = new Map<string, string>();
|
|
121
|
+
const targets = new Set<string>();
|
|
122
|
+
for (const [from, to] of batchArg.moves) {
|
|
123
|
+
if (from === to || !this.files.has(from) || targets.has(to) || (this.exists(to) && !batchArg.moves.has(to))) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
moves.set(from, to);
|
|
127
|
+
targets.add(to);
|
|
128
|
+
}
|
|
129
|
+
if (moves.size > 0) {
|
|
130
|
+
this.rewriteSpecifiers(ruleIdArg, moves, batchArg.redirects);
|
|
131
|
+
const texts = new Map([...moves.keys()].map((fromArg) => [fromArg, this.files.get(fromArg)!]));
|
|
132
|
+
for (const [from, to] of moves) {
|
|
133
|
+
this.files.delete(from);
|
|
134
|
+
if (this.created.delete(from)) {
|
|
135
|
+
this.created.add(to);
|
|
136
|
+
} else {
|
|
137
|
+
const origin = this.originOf.get(from) ?? from;
|
|
138
|
+
this.originOf.delete(from);
|
|
139
|
+
if (origin !== to) {
|
|
140
|
+
this.originOf.set(to, origin);
|
|
141
|
+
}
|
|
142
|
+
if (origin === from) {
|
|
143
|
+
this.vacated.add(from);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const changed = this.changedBy.get(from);
|
|
147
|
+
this.changedBy.delete(from);
|
|
148
|
+
if (changed) {
|
|
149
|
+
this.changedBy.set(to, changed);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const [from, to] of moves) {
|
|
153
|
+
this.files.set(to, texts.get(from)!);
|
|
154
|
+
this.vacated.delete(to);
|
|
155
|
+
}
|
|
156
|
+
applied += moves.size;
|
|
157
|
+
}
|
|
158
|
+
return applied;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Rewrites every relative module specifier whose importing file or target moves, keeping its
|
|
163
|
+
* extension style. A specifier resolves to any tracked file (sources, `.d.ts`, `.json`, `.css`, …),
|
|
164
|
+
* so a moved file's imports of files that stay keep pointing at them. Barrel re-exports of a
|
|
165
|
+
* redirected target point at the redirect instead.
|
|
166
|
+
*/
|
|
167
|
+
private rewriteSpecifiers(ruleIdArg: string, movesArg: Map<string, string>, redirectsArg: Map<string, string>): void {
|
|
168
|
+
const known = (fileArg: string): boolean => this.files.has(fileArg) || (this.trackedFiles.has(fileArg) && !this.vacated.has(fileArg));
|
|
169
|
+
const project = this.project();
|
|
170
|
+
for (const [file, text] of [...this.files]) {
|
|
171
|
+
if (!isTypeScriptSource(file)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const newFile = movesArg.get(file) ?? file;
|
|
175
|
+
const sourceFile = project.sourceFile(file);
|
|
176
|
+
const edits = new SourceEdits();
|
|
177
|
+
for (const literal of ast.moduleSpecifierLiterals(sourceFile)) {
|
|
178
|
+
const resolved = resolveSpecifier(file, literal.text, known);
|
|
179
|
+
if (!resolved) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
let target = movesArg.get(resolved.target) ?? resolved.target;
|
|
183
|
+
let viaFolder = resolved.viaFolder;
|
|
184
|
+
const redirect = redirectsArg.get(resolved.target);
|
|
185
|
+
if (redirect && redirect !== newFile && ts.isExportDeclaration(literal.parent) && baseOf(newFile) === 'index.ts') {
|
|
186
|
+
target = redirect;
|
|
187
|
+
viaFolder = true;
|
|
188
|
+
}
|
|
189
|
+
if (newFile === file && target === resolved.target) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const specifier = specifierFor(newFile, target, resolved.style, viaFolder);
|
|
193
|
+
if (specifier !== literal.text) {
|
|
194
|
+
edits.add({ start: literal.getStart() + 1, end: literal.end - 1, text: specifier });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (edits.size > 0) {
|
|
198
|
+
this.files.set(file, edits.apply(text));
|
|
199
|
+
this.markChanged(file, ruleIdArg);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** The tree's changes against the repository it was read from. */
|
|
205
|
+
public plan(): plugins.interfaces.IStandardFixPlan {
|
|
206
|
+
const moves = [...this.originOf].map(([to, from]) => ({ from, to })).sort((a, b) => a.from.localeCompare(b.from));
|
|
207
|
+
const created = [...this.created].sort();
|
|
208
|
+
const edited: plugins.interfaces.IStandardFixPlan['edited'] = [];
|
|
209
|
+
for (const [file, ruleIds] of [...this.changedBy].sort(([a], [b]) => a.localeCompare(b))) {
|
|
210
|
+
if (this.created.has(file)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const origin = this.originOf.get(file) ?? file;
|
|
214
|
+
if (this.files.get(file) !== this.originalTexts.get(origin)) {
|
|
215
|
+
edited.push({ file, ruleIds: [...ruleIds].sort() });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { moves, created, edited };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { checkStandard } from './checker.js';
|
|
3
|
+
import { loadStandardProject } from './classes.standardproject.js';
|
|
4
|
+
import { StandardFixer } from './classes.standardfixer.js';
|
|
5
|
+
import { openStandardConfig } from './config.js';
|
|
6
|
+
import { StandardUsageError } from './errors.js';
|
|
7
|
+
import { renderFixResult, renderStandardReport } from './output.js';
|
|
8
|
+
import { initBaselines, updateBaselines } from './ratchet.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Registers `wcctools check` and `wcctools fix`. Both work on the current directory, the
|
|
12
|
+
* repository root that holds `.smartconfig.json`. Exit codes: 0 ok, 1 failing findings or a
|
|
13
|
+
* refused baseline update, 2 configuration or usage error (StandardConfigError/StandardUsageError).
|
|
14
|
+
*/
|
|
15
|
+
export const registerStandardCommands = (out: plugins.smartconsole.SmartConsole): void => {
|
|
16
|
+
out.cli.command({
|
|
17
|
+
name: 'check',
|
|
18
|
+
description: 'Check the catalog against the wcctools component standard (https://code.foss.global/design.estate/wcctools/src/branch/main/docs/standard.md)',
|
|
19
|
+
options: {
|
|
20
|
+
json: { type: 'boolean', description: 'Print the report as one JSON document (IStandardReport) and nothing else' },
|
|
21
|
+
verbose: { type: 'boolean', description: 'Also list info findings, findings covered by the allowlist or baselines, and baseline slack' },
|
|
22
|
+
initBaseline: { type: 'boolean', description: 'Record the current error counts as standard.baselines (only while none exist)' },
|
|
23
|
+
updateBaseline: { type: 'boolean', description: 'Lower standard.baselines to the current counts; refuses to raise any count' },
|
|
24
|
+
},
|
|
25
|
+
}, async ({ options }) => {
|
|
26
|
+
if (options.initBaseline && options.updateBaseline) {
|
|
27
|
+
throw new StandardUsageError('--init-baseline and --update-baseline exclude each other');
|
|
28
|
+
}
|
|
29
|
+
const rootDir = process.cwd();
|
|
30
|
+
const opened = openStandardConfig(rootDir);
|
|
31
|
+
const notes: string[] = [];
|
|
32
|
+
let report: plugins.interfaces.IStandardReport;
|
|
33
|
+
let refused = false;
|
|
34
|
+
if (options.initBaseline || options.updateBaseline) {
|
|
35
|
+
const result = options.initBaseline ? await initBaselines(rootDir, opened) : await updateBaselines(rootDir, opened);
|
|
36
|
+
report = result.report;
|
|
37
|
+
if (result.raised.length > 0) {
|
|
38
|
+
refused = true;
|
|
39
|
+
for (const raise of result.raised) {
|
|
40
|
+
await out.error(`refusing to raise the ${raise.ruleId} baseline of ${raise.file}: ${raise.baseline} → ${raise.current}`);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
notes.push(`standard.baselines written: ${result.writtenFiles} file(s)`);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
report = checkStandard(await loadStandardProject(rootDir), opened.loaded);
|
|
47
|
+
}
|
|
48
|
+
if (options.json) {
|
|
49
|
+
await out.log(JSON.stringify(report));
|
|
50
|
+
} else {
|
|
51
|
+
for (const line of [...renderStandardReport(report, !!options.verbose), ...notes]) {
|
|
52
|
+
await out.log(line);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
process.exitCode = report.ok && !refused ? 0 : 1;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
out.cli.command({
|
|
59
|
+
name: 'fix',
|
|
60
|
+
description: 'Fix findings of the wcctools component standard with span edits and git mv; needs a clean work tree, never commits',
|
|
61
|
+
options: {
|
|
62
|
+
rule: { type: 'strings', description: 'Fix only this rule (repeatable); opt-in rules run only when named' },
|
|
63
|
+
dryRun: { type: 'boolean', description: 'Show the changes without writing anything' },
|
|
64
|
+
json: { type: 'boolean', description: 'Print the result as one JSON document (IStandardFixResult) and nothing else' },
|
|
65
|
+
verbose: { type: 'boolean', description: 'Also list info and covered findings of the remaining check' },
|
|
66
|
+
},
|
|
67
|
+
}, async ({ options }) => {
|
|
68
|
+
const rootDir = process.cwd();
|
|
69
|
+
const opened = openStandardConfig(rootDir);
|
|
70
|
+
const result = await new StandardFixer({
|
|
71
|
+
rootDir,
|
|
72
|
+
loaded: opened.loaded,
|
|
73
|
+
rules: options.rule,
|
|
74
|
+
dryRun: !!options.dryRun,
|
|
75
|
+
configWriter: opened.writer,
|
|
76
|
+
}).run();
|
|
77
|
+
if (options.json) {
|
|
78
|
+
await out.log(JSON.stringify(result));
|
|
79
|
+
} else {
|
|
80
|
+
for (const line of renderFixResult(result, !!options.verbose)) {
|
|
81
|
+
await out.log(line);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
process.exitCode = result.remaining.ok ? 0 : 1;
|
|
85
|
+
});
|
|
86
|
+
};
|