@markuplint/ml-core 5.0.0-alpha.1 → 5.0.0-alpha.3

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.
Files changed (46) hide show
  1. package/ARCHITECTURE.ja.md +158 -5
  2. package/ARCHITECTURE.md +209 -6
  3. package/CHANGELOG.md +25 -0
  4. package/docs/ml-dom/document.ja.md +7 -14
  5. package/docs/ml-dom/document.md +7 -14
  6. package/docs/ml-dom/element.ja.md +9 -24
  7. package/docs/ml-dom/element.md +9 -24
  8. package/docs/rule-system.ja.md +1 -1
  9. package/docs/rule-system.md +1 -1
  10. package/lib/cursor-offset.d.ts +13 -0
  11. package/lib/cursor-offset.js +34 -0
  12. package/lib/fix-applier.d.ts +32 -0
  13. package/lib/fix-applier.js +75 -0
  14. package/lib/index.d.ts +3 -0
  15. package/lib/index.js +2 -0
  16. package/lib/ml-core.d.ts +53 -8
  17. package/lib/ml-core.js +197 -59
  18. package/lib/ml-dom/helper/get-indent.d.ts +0 -1
  19. package/lib/ml-dom/helper/get-indent.js +5 -18
  20. package/lib/ml-dom/node/attr.d.ts +2 -13
  21. package/lib/ml-dom/node/attr.js +3 -35
  22. package/lib/ml-dom/node/block.js +2 -1
  23. package/lib/ml-dom/node/character-data.d.ts +35 -0
  24. package/lib/ml-dom/node/character-data.js +35 -6
  25. package/lib/ml-dom/node/document.d.ts +2 -27
  26. package/lib/ml-dom/node/document.js +7 -46
  27. package/lib/ml-dom/node/dom-token-list.d.ts +0 -1
  28. package/lib/ml-dom/node/dom-token-list.js +3 -3
  29. package/lib/ml-dom/node/element-close-tag.d.ts +1 -1
  30. package/lib/ml-dom/node/element-close-tag.js +2 -16
  31. package/lib/ml-dom/node/element.d.ts +2 -19
  32. package/lib/ml-dom/node/element.js +3 -65
  33. package/lib/ml-dom/token/token.d.ts +3 -18
  34. package/lib/ml-dom/token/token.js +7 -28
  35. package/lib/ml-rule/index.d.ts +1 -0
  36. package/lib/ml-rule/index.js +1 -0
  37. package/lib/ml-rule/ml-rule-context.d.ts +2 -31
  38. package/lib/ml-rule/ml-rule-context.js +18 -18
  39. package/lib/ml-rule/ml-rule.d.ts +6 -11
  40. package/lib/ml-rule/ml-rule.js +36 -37
  41. package/lib/ml-rule/rule-fixer.d.ts +20 -0
  42. package/lib/ml-rule/rule-fixer.js +38 -0
  43. package/lib/ml-rule/types.d.ts +1 -2
  44. package/lib/test/index.d.ts +1 -10
  45. package/lib/test/index.js +0 -11
  46. package/package.json +12 -12
@@ -1,7 +1,9 @@
1
- import { deleteUndefProp } from '@markuplint/ml-config';
2
1
  // @ts-ignore
3
2
  import { isPlainObject } from 'is-plain-object';
3
+ import { RuleFixer } from './rule-fixer.js';
4
4
  import { MLRuleContext } from './ml-rule-context.js';
5
+ // Stateless — safe to share across all rule instances
6
+ const sharedFixer = new RuleFixer();
5
7
  /**
6
8
  * Represents a single markuplint rule that can verify documents and report violations.
7
9
  *
@@ -10,14 +12,13 @@ import { MLRuleContext } from './ml-rule-context.js';
10
12
  */
11
13
  export class MLRule {
12
14
  /**
13
- * For virtual rules, the name of the base rule whose verify/fix logic is reused.
15
+ * For virtual rules, the name of the base rule whose verify logic is reused.
14
16
  * When set, violations report this as `ruleId` for backwards compatibility.
15
17
  */
16
18
  baseRuleId;
17
19
  defaultOptions;
18
20
  defaultSeverity;
19
21
  defaultValue;
20
- #f;
21
22
  /**
22
23
  * For multi-entry named nodeRules, the group name shared by all derived virtual rules.
23
24
  * Allows `rules["groupName"]: false` to disable all rules in the group.
@@ -42,16 +43,15 @@ export class MLRule {
42
43
  this.defaultValue = (o.defaultValue === undefined ? true : o.defaultValue);
43
44
  this.defaultOptions = o.defaultOptions;
44
45
  this.#v = o.verify;
45
- this.#f = o.fix;
46
46
  }
47
47
  /**
48
- * Creates a virtual rule that reuses this rule's verify/fix logic
48
+ * Creates a virtual rule that reuses this rule's verify logic
49
49
  * under a different name (alias). Used by named nodeRules to produce
50
50
  * independent rule instances that can be individually configured.
51
51
  *
52
52
  * @param aliasName - The alias name (must contain `/`)
53
53
  * @param options - Override options for the virtual rule
54
- * @returns A new MLRule instance sharing the same verify/fix logic
54
+ * @returns A new MLRule instance sharing the same verify logic
55
55
  */
56
56
  createAlias(aliasName, options) {
57
57
  return new MLRule({
@@ -63,16 +63,8 @@ export class MLRule {
63
63
  defaultValue: this.defaultValue,
64
64
  defaultOptions: this.defaultOptions,
65
65
  verify: this.#v,
66
- fix: this.#f,
67
66
  });
68
67
  }
69
- /**
70
- * The following getter is unused internally,
71
- * only for extending from 3rd party library
72
- */
73
- get f() {
74
- return this.#f;
75
- }
76
68
  /**
77
69
  * The following getter is unused internally,
78
70
  * only for extending from 3rd party library
@@ -89,11 +81,11 @@ export class MLRule {
89
81
  * @returns The global rule info with node and child-node overrides
90
82
  */
91
83
  getRuleInfo(ruleSet, ruleName) {
92
- const info = this._optimize(ruleSet.rules, ruleName);
84
+ const info = this.#optimize(ruleSet.rules, ruleName);
93
85
  return {
94
86
  ...info,
95
- nodeRules: ruleSet.nodeRules.map(r => this._optimize(r.rules, ruleName)).filter(r => !r.disabled),
96
- childNodeRules: ruleSet.childNodeRules.map(r => this._optimize(r.rules, ruleName)).filter(r => !r.disabled),
87
+ nodeRules: ruleSet.nodeRules.map(r => this.#optimize(r.rules, ruleName)).filter(r => !r.disabled),
88
+ childNodeRules: ruleSet.childNodeRules.map(r => this.#optimize(r.rules, ruleName)).filter(r => !r.disabled),
97
89
  };
98
90
  }
99
91
  /**
@@ -135,12 +127,13 @@ export class MLRule {
135
127
  };
136
128
  }
137
129
  /**
138
- * Executes this rule's verify (and optionally fix) function against a document,
130
+ * Executes this rule's verify function against a document,
139
131
  * then collects and returns the resulting violations.
132
+ * When `fix` is true, fix callbacks on reports are executed to produce {@link FixData}.
140
133
  *
141
134
  * @param document - The parsed document to verify
142
135
  * @param locale - The locale set for translating violation messages
143
- * @param fix - Whether to also run the fix function
136
+ * @param fix - Whether to execute fix callbacks and attach FixData to violations
144
137
  * @returns An array of violations found by this rule
145
138
  */
146
139
  async verify(
@@ -150,13 +143,19 @@ export class MLRule {
150
143
  const context = new MLRuleContext(document, locale);
151
144
  const providableContext = context.provide();
152
145
  await this.#v(providableContext);
153
- if (this.#f && fix) {
154
- await this.#f(providableContext);
155
- }
156
146
  const ruleId = this.baseRuleId ?? this.name;
157
147
  // Only include name and specConformance for virtual rules (named nodeRules)
158
148
  const aliasName = this.baseRuleId ? this.name : undefined;
159
- const violation = context.reports.map(report => {
149
+ const violations = context.reports.map(report => {
150
+ // Execute fix callback if fix mode is enabled
151
+ let fixData;
152
+ if (fix && report.fix) {
153
+ const edits = report.fix(sharedFixer);
154
+ const editArray = Array.isArray(edits) ? edits : [edits];
155
+ if (editArray.length > 0) {
156
+ fixData = { edits: editArray };
157
+ }
158
+ }
160
159
  if ('scope' in report) {
161
160
  let line = report.scope.startLine;
162
161
  let col = report.scope.startCol;
@@ -166,38 +165,38 @@ export class MLRule {
166
165
  col = report.col;
167
166
  raw = report.raw;
168
167
  }
169
- const violation = {
168
+ return {
170
169
  severity: report.scope.rule.severity,
171
170
  message: report.message,
172
171
  line,
173
172
  col,
174
173
  raw,
175
174
  ruleId,
176
- name: aliasName,
177
- specConformance: this.specConformance,
178
- reason: report.scope.rule.reason ?? document.rule.reason,
175
+ ...(aliasName != null && { name: aliasName }),
176
+ ...(this.specConformance != null && { specConformance: this.specConformance }),
177
+ ...((report.scope.rule.reason ?? document.rule.reason)
178
+ ? { reason: report.scope.rule.reason ?? document.rule.reason }
179
+ : {}),
180
+ ...(fixData != null && { fix: fixData }),
179
181
  };
180
- deleteUndefProp(violation);
181
- return violation;
182
182
  }
183
- const violation = {
183
+ return {
184
184
  severity: document.rule.severity,
185
185
  message: report.message,
186
186
  line: report.line,
187
187
  col: report.col,
188
188
  raw: report.raw,
189
189
  ruleId,
190
- name: aliasName,
191
- specConformance: this.specConformance,
192
- reason: document.rule.reason,
190
+ ...(aliasName != null && { name: aliasName }),
191
+ ...(this.specConformance != null && { specConformance: this.specConformance }),
192
+ ...(document.rule.reason ? { reason: document.rule.reason } : {}),
193
+ ...(fixData != null && { fix: fixData }),
193
194
  };
194
- deleteUndefProp(violation);
195
- return violation;
196
195
  });
197
196
  document.setRule(null);
198
- return violation;
197
+ return violations;
199
198
  }
200
- _optimize(rules, ruleName) {
199
+ #optimize(rules, ruleName) {
201
200
  const rule = (rules?.[ruleName] ?? false);
202
201
  const info = this.optimizeOption(rule);
203
202
  return info;
@@ -0,0 +1,20 @@
1
+ import type { FixToken, IRuleFixer, TextEdit } from '@markuplint/ml-config';
2
+ /**
3
+ * Stateless implementation of {@link IRuleFixer}.
4
+ * Provides helper methods for building {@link TextEdit} objects
5
+ * inside rule fix callbacks.
6
+ */
7
+ export declare class RuleFixer implements IRuleFixer {
8
+ /** @see {@link IRuleFixer.replaceText} */
9
+ replaceText(token: FixToken, text: string): TextEdit;
10
+ /** @see {@link IRuleFixer.replaceRange} */
11
+ replaceRange(range: readonly [number, number], text: string): TextEdit;
12
+ /** @see {@link IRuleFixer.insertBefore} */
13
+ insertBefore(token: Pick<FixToken, 'startOffset'>, text: string): TextEdit;
14
+ /** @see {@link IRuleFixer.insertAfter} */
15
+ insertAfter(token: FixToken, text: string): TextEdit;
16
+ /** @see {@link IRuleFixer.remove} */
17
+ remove(token: FixToken): TextEdit;
18
+ /** @see {@link IRuleFixer.removeRange} */
19
+ removeRange(range: readonly [number, number]): TextEdit;
20
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Stateless implementation of {@link IRuleFixer}.
3
+ * Provides helper methods for building {@link TextEdit} objects
4
+ * inside rule fix callbacks.
5
+ */
6
+ export class RuleFixer {
7
+ /** @see {@link IRuleFixer.replaceText} */
8
+ replaceText(token, text) {
9
+ return {
10
+ range: [token.startOffset, token.startOffset + token.raw.length],
11
+ text,
12
+ };
13
+ }
14
+ /** @see {@link IRuleFixer.replaceRange} */
15
+ replaceRange(range, text) {
16
+ return { range, text };
17
+ }
18
+ /** @see {@link IRuleFixer.insertBefore} */
19
+ insertBefore(token, text) {
20
+ return { range: [token.startOffset, token.startOffset], text };
21
+ }
22
+ /** @see {@link IRuleFixer.insertAfter} */
23
+ insertAfter(token, text) {
24
+ const end = token.startOffset + token.raw.length;
25
+ return { range: [end, end], text };
26
+ }
27
+ /** @see {@link IRuleFixer.remove} */
28
+ remove(token) {
29
+ return {
30
+ range: [token.startOffset, token.startOffset + token.raw.length],
31
+ text: '',
32
+ };
33
+ }
34
+ /** @see {@link IRuleFixer.removeRange} */
35
+ removeRange(range) {
36
+ return { range, text: '' };
37
+ }
38
+ }
@@ -3,7 +3,7 @@ import type { Attr, Element } from '../ml-dom/index.js';
3
3
  import type { Translator } from '@markuplint/i18n';
4
4
  import type { PlainData, Report, RuleConfigValue, Severity } from '@markuplint/ml-config';
5
5
  /**
6
- * The definition of a markuplint rule, including verification logic, optional fix logic,
6
+ * The definition of a markuplint rule, including verification logic
7
7
  * and default configuration values.
8
8
  *
9
9
  * @template T - The type of the rule's configuration value (defaults to boolean)
@@ -17,7 +17,6 @@ export type RuleSeed<T extends RuleConfigValue = boolean, O extends PlainData =
17
17
  readonly defaultValue?: T;
18
18
  readonly defaultOptions?: O;
19
19
  verify(context: ReturnType<MLRuleContext<T, O>['provide']>): void | Promise<void>;
20
- fix?(context: ReturnType<MLRuleContext<T, O>['provide']>): void | Promise<void>;
21
20
  };
22
21
  /**
23
22
  * A generic checker function that produces a violation report from parameters.
@@ -1,7 +1,6 @@
1
1
  import type { MLElement } from '../ml-dom/node/element.js';
2
2
  import type { MLNode } from '../ml-dom/node/node.js';
3
- import type { MLToken } from '../ml-dom/token/token.js';
4
- import type { MLASTNode, MLASTToken, MLParser } from '@markuplint/ml-ast';
3
+ import type { MLASTNode, MLParser } from '@markuplint/ml-ast';
5
4
  import type { Config, PlainData, Pretender, RuleConfigValue } from '@markuplint/ml-config';
6
5
  import type { MLMLSpec } from '@markuplint/ml-spec';
7
6
  import { MLDocument } from '../ml-dom/node/document.js';
@@ -38,14 +37,6 @@ export declare function createTestDocument<T extends RuleConfigValue = any, O ex
38
37
  * @returns A readonly array of all nodes in the parsed document
39
38
  */
40
39
  export declare function createTestNodeList(sourceCode: string, options?: CreateTestOptions): readonly MLNode<any, any, MLASTNode>[];
41
- /**
42
- * Parses markup source code and returns the flat list of tokens.
43
- *
44
- * @param sourceCode - The markup source code to parse
45
- * @param options - Options for parser, config, specs, and pretenders
46
- * @returns A readonly array of all tokens in the parsed document
47
- */
48
- export declare function createTestTokenList(sourceCode: string, options?: CreateTestOptions): readonly MLToken<MLASTToken>[];
49
40
  /**
50
41
  * Parses markup source code and returns the first element node.
51
42
  * Throws if the source does not produce an element as its first node.
package/lib/test/index.js CHANGED
@@ -35,17 +35,6 @@ export function createTestNodeList(sourceCode, options) {
35
35
  const document = createTestDocument(sourceCode, options);
36
36
  return document.nodeList;
37
37
  }
38
- /**
39
- * Parses markup source code and returns the flat list of tokens.
40
- *
41
- * @param sourceCode - The markup source code to parse
42
- * @param options - Options for parser, config, specs, and pretenders
43
- * @returns A readonly array of all tokens in the parsed document
44
- */
45
- export function createTestTokenList(sourceCode, options) {
46
- const document = createTestDocument(sourceCode, options);
47
- return document.getTokenList();
48
- }
49
38
  /**
50
39
  * Parses markup source code and returns the first element node.
51
40
  * Throws if the source does not produce an element as its first node.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/ml-core",
3
- "version": "5.0.0-alpha.1",
3
+ "version": "5.0.0-alpha.3",
4
4
  "description": "The core module of markuplint",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -30,20 +30,20 @@
30
30
  "./lib/configs.js": "./lib/configs.browser.js"
31
31
  },
32
32
  "dependencies": {
33
- "@markuplint/config-presets": "5.0.0-alpha.1",
34
- "@markuplint/html-parser": "5.0.0-alpha.1",
35
- "@markuplint/html-spec": "5.0.0-alpha.1",
36
- "@markuplint/i18n": "5.0.0-alpha.1",
37
- "@markuplint/ml-ast": "5.0.0-alpha.1",
38
- "@markuplint/ml-config": "5.0.0-alpha.1",
39
- "@markuplint/ml-spec": "5.0.0-alpha.1",
40
- "@markuplint/parser-utils": "5.0.0-alpha.1",
41
- "@markuplint/selector": "5.0.0-alpha.1",
42
- "@markuplint/shared": "5.0.0-alpha.1",
33
+ "@markuplint/config-presets": "5.0.0-alpha.3",
34
+ "@markuplint/html-parser": "5.0.0-alpha.3",
35
+ "@markuplint/html-spec": "5.0.0-alpha.3",
36
+ "@markuplint/i18n": "5.0.0-alpha.3",
37
+ "@markuplint/ml-ast": "5.0.0-alpha.3",
38
+ "@markuplint/ml-config": "5.0.0-alpha.3",
39
+ "@markuplint/ml-spec": "5.0.0-alpha.3",
40
+ "@markuplint/parser-utils": "5.0.0-alpha.3",
41
+ "@markuplint/selector": "5.0.0-alpha.3",
42
+ "@markuplint/shared": "5.0.0-alpha.3",
43
43
  "@types/debug": "4.1.12",
44
44
  "debug": "4.4.3",
45
45
  "is-plain-object": "5.0.0",
46
46
  "type-fest": "5.4.4"
47
47
  },
48
- "gitHead": "78a295e73a097a1ce09c777c06fa21ab68136387"
48
+ "gitHead": "2fbdf26daa3d021ac628ccc2f59f0eeae6ddd53d"
49
49
  }