wikilint 2.45.0 → 2.46.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/dist/base.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Range, Position, ColorInformation, ColorPresentation, CompletionItem as CompletionItemBase, CompletionItemKind, FoldingRange, DocumentLink, Location, WorkspaceEdit, Diagnostic, Hover, SignatureHelp, InlayHint, CodeAction, DocumentSymbol } from 'vscode-languageserver-types';
1
+ import type { Range, Position, ColorInformation, ColorPresentation, CompletionItem as CompletionItemBase, CompletionItemKind, FoldingRange, DocumentLink, DocumentHighlight, Location, WorkspaceEdit, Diagnostic, Hover, SignatureHelp, InlayHint, CodeAction, DocumentSymbol } from 'vscode-languageserver-types';
2
2
  export interface Config {
3
3
  ext: string[];
4
4
  readonly html: [string[], string[], string[]];
@@ -177,6 +177,14 @@ export interface LanguageService {
177
177
  * @param text source Wikitext / 源代码
178
178
  */
179
179
  provideLinks(text: string): Promise<DocumentLink[]>;
180
+ /**
181
+ * Provide document highlights
182
+ *
183
+ * 提供文档高亮
184
+ * @param text source Wikitext / 源代码
185
+ * @param position position / 位置
186
+ */
187
+ provideDocumentHighlights(text: string, position: Position): Promise<DocumentHighlight[] | undefined>;
180
188
  /**
181
189
  * Provide references
182
190
  *
package/dist/base.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Range, Position, ColorInformation, ColorPresentation, CompletionItem as CompletionItemBase, CompletionItemKind, FoldingRange, DocumentLink, Location, WorkspaceEdit, Diagnostic, Hover, SignatureHelp, InlayHint, CodeAction, DocumentSymbol } from 'vscode-languageserver-types';
1
+ import type { Range, Position, ColorInformation, ColorPresentation, CompletionItem as CompletionItemBase, CompletionItemKind, FoldingRange, DocumentLink, DocumentHighlight, Location, WorkspaceEdit, Diagnostic, Hover, SignatureHelp, InlayHint, CodeAction, DocumentSymbol } from 'vscode-languageserver-types';
2
2
  export interface Config {
3
3
  ext: string[];
4
4
  readonly html: [string[], string[], string[]];
@@ -177,6 +177,14 @@ export interface LanguageService {
177
177
  * @param text source Wikitext / 源代码
178
178
  */
179
179
  provideLinks(text: string): Promise<DocumentLink[]>;
180
+ /**
181
+ * Provide document highlights
182
+ *
183
+ * 提供文档高亮
184
+ * @param text source Wikitext / 源代码
185
+ * @param position position / 位置
186
+ */
187
+ provideDocumentHighlights(text: string, position: Position): Promise<DocumentHighlight[] | undefined>;
180
188
  /**
181
189
  * Provide references
182
190
  *
@@ -41,7 +41,7 @@ const filterGadget = (id) => {
41
41
  const n = Number(id);
42
42
  return n < 2300 || n > 2303; // Gadget, Gadget talk, Gadget definition, Gadget definition talk
43
43
  };
44
- const pkg = "wikilint", version = "2.45.0";
44
+ const pkg = 'wikilint', version = '2.46.0';
45
45
  /**
46
46
  * Get the parser configuration for a Wikimedia Foundation project.
47
47
  * @param site site nickname
package/dist/lib/lsp.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import Parser from '../index';
2
2
  import { Token } from '../src/index';
3
- import type { Range, Position, ColorInformation, ColorPresentation, FoldingRange, DocumentLink, Location, WorkspaceEdit, Diagnostic as DiagnosticBase, TextEdit, Hover, SignatureHelp, InlayHint, CodeAction, DocumentSymbol } from 'vscode-languageserver-types';
3
+ import type { Range, Position, ColorInformation, ColorPresentation, FoldingRange, DocumentLink, Location, DocumentHighlight, WorkspaceEdit, Diagnostic as DiagnosticBase, TextEdit, Hover, SignatureHelp, InlayHint, CodeAction, DocumentSymbol } from 'vscode-languageserver-types';
4
4
  import type { Config, LanguageService as LanguageServiceBase, CompletionItem, SignatureData } from '../base';
5
5
  import type { AttributeToken } from '../internal';
6
6
  import type { TokenTypeMap, SelectedTokenTypes } from '../map';
@@ -71,6 +71,15 @@ export declare class LanguageService implements LanguageServiceBase {
71
71
  * @param text source Wikitext / 源代码
72
72
  */
73
73
  provideLinks(text: string): Promise<DocumentLink[]>;
74
+ /**
75
+ * Provide document highlights
76
+ *
77
+ * 提供文档高亮
78
+ * @param text source Wikitext / 源代码
79
+ * @param position position / 位置
80
+ * @since v1.46.0
81
+ */
82
+ provideDocumentHighlights(text: string, position: Position): Promise<DocumentHighlight[] | undefined>;
74
83
  /**
75
84
  * Provide references
76
85
  *
package/dist/lib/lsp.js CHANGED
@@ -21,6 +21,7 @@ const util_1 = __importDefault(require("util"));
21
21
  const child_process_1 = require("child_process");
22
22
  const crypto_1 = require("crypto");
23
23
  const stylelint_util_1 = require("@bhsd/stylelint-util");
24
+ const color_1 = require("@bhsd/common/color");
24
25
  const search_1 = __importDefault(require("../vendor/search"));
25
26
  const constants_2 = require("../util/constants");
26
27
  const document_1 = require("./document");
@@ -30,15 +31,17 @@ const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']
30
31
  'template-name',
31
32
  'link-target',
32
33
  'parameter-key',
33
- ]), referenceTypes = new Set([
34
+ ]), highlightTypes = new Set([
34
35
  'ext',
35
36
  'html',
36
37
  'attr-key',
38
+ 'magic-word-name',
39
+ ...renameTypes,
40
+ ]), referenceTypes = new Set([
37
41
  'image-parameter',
38
42
  'heading-title',
39
43
  'heading',
40
- 'magic-word-name',
41
- ...renameTypes,
44
+ ...highlightTypes,
42
45
  ]), plainTypes = new Set(['text', 'comment', 'noinclude', 'include']), rawLilyPondCommands = new Set(['paper', 'book', 'bookpart', 'score', 'markuplist'].map(c => `\\${c}`)), cssSelector = ['ext', 'html', 'table'].map(s => `${s}-attr#style`).join();
43
46
  const getLinkRegex = (0, common_1.getRegex)(protocol => new RegExp(`^(?:${protocol}|//)`, 'iu'));
44
47
  /**
@@ -168,8 +171,10 @@ const getName = (token) => {
168
171
  return token.level;
169
172
  case 'heading-title':
170
173
  return parentNode.level;
171
- case 'parameter-key':
172
- return `${parentNode.parentNode.name}|${parentNode.name}`;
174
+ case 'parameter-key': {
175
+ const { type: t, name: n } = parentNode.parentNode;
176
+ return n + (t === 'template' ? '|' : '#') + parentNode.name;
177
+ }
173
178
  case 'ext':
174
179
  case 'html':
175
180
  case 'image-parameter':
@@ -178,6 +183,22 @@ const getName = (token) => {
178
183
  return parentNode.name;
179
184
  }
180
185
  };
186
+ /**
187
+ * Get the tokens that match the given token.
188
+ * @param root
189
+ * @param node the token to match
190
+ * @param types accepted token types
191
+ */
192
+ const getMatchingTokens = (root, node, types) => {
193
+ const { type } = node, refName = getRefName(node), refGroup = getRefGroup(node);
194
+ if (!refName && !refGroup && !types.has(type)) {
195
+ return undefined;
196
+ }
197
+ const name = getName(node), refs = root.querySelectorAll(type === 'heading-title' ? 'heading' : type).filter(token => type === 'attr-value'
198
+ ? getRefName(token) === refName || getRefGroup(token) === refGroup
199
+ : getName(token) === name);
200
+ return refs.length === 0 ? undefined : refs.reverse();
201
+ };
181
202
  /**
182
203
  * Get the quick fix data.
183
204
  * @param root root token
@@ -272,7 +293,7 @@ const partialParse = async (wikitext, watch, include, config = index_1.default.g
272
293
  };
273
294
  /* NOT FOR BROWSER ONLY */
274
295
  /** @see https://www.npmjs.com/package/stylelint-config-recommended */
275
- const cssRules = { 'block-no-empty': null }, colors = Object.keys(common_1.colorsNamed), sources = { 'invalid-css': 'css', 'invalid-math': 'texvc' }, jsonSelector = constants_2.jsonTags.map(s => `ext#${s}`).join(), scores = new Map();
296
+ const cssRules = { 'block-no-empty': null }, colors = Object.keys(color_1.colorsNamed), sources = { 'invalid-css': 'css', 'invalid-math': 'texvc' }, jsonSelector = constants_2.jsonTags.map(s => `ext#${s}`).join(), scores = new Map();
276
297
  /**
277
298
  * Correct the position of an error.
278
299
  * @param height
@@ -911,7 +932,8 @@ class LanguageService {
911
932
  catch (e) {
912
933
  const { stderr } = e;
913
934
  if (stderr) {
914
- const re = new RegExp(String.raw `^${file}:(\d+):(\d+): error: (.+)$`, 'gmu'), lilypondErrors = [...stderr.matchAll(re)].map(([, line, col, msg]) => {
935
+ const re = new RegExp(String.raw `^${file}:(\d+):(\d+): error: (.+)$`, 'gmu'), lilypondErrors = [...stderr.matchAll(re)]
936
+ .map(([, line, col, msg]) => {
915
937
  const { offsetHeight, offsetWidth } = token.lastChild, pos = adjustPos(offsetHeight, offsetWidth, Number(line) - 1, Number(col) - 1);
916
938
  return {
917
939
  line: pos[0],
@@ -1122,6 +1144,37 @@ class LanguageService {
1122
1144
  })
1123
1145
  .filter(link => link !== false);
1124
1146
  }
1147
+ /**
1148
+ * Provide document highlights
1149
+ *
1150
+ * 提供文档高亮
1151
+ * @param text source Wikitext / 源代码
1152
+ * @param position position / 位置
1153
+ * @since v1.46.0
1154
+ */
1155
+ async provideDocumentHighlights(text, position) {
1156
+ const root = await this.#queue(text), index = root.indexFromPos(position.line, position.character), node = root.elementFromIndex(index), type = node?.type;
1157
+ if (!node || (type === 'ext' || type === 'html') && !/\w/u.test(text.charAt(index))) {
1158
+ return undefined;
1159
+ }
1160
+ return getMatchingTokens(root, node, highlightTypes)?.map((token) => {
1161
+ const range = createNodeRange(token);
1162
+ if (type === 'ext' || type === 'html') {
1163
+ const { start } = range;
1164
+ start.character += token.closing ? 2 : 1;
1165
+ return {
1166
+ range: {
1167
+ start,
1168
+ end: {
1169
+ line: start.line,
1170
+ character: start.character + token.name.length,
1171
+ },
1172
+ },
1173
+ };
1174
+ }
1175
+ return { range };
1176
+ });
1177
+ }
1125
1178
  /**
1126
1179
  * Provide references
1127
1180
  *
@@ -1132,16 +1185,19 @@ class LanguageService {
1132
1185
  async provideReferences(text, position) {
1133
1186
  const root = await this.#queue(text), { offsetNode, offset } = caretPositionFromWord(root, this.#text, position), element = offsetNode.type === 'text' ? offsetNode.parentNode : offsetNode, node = offset === 0 && (element.is('ext-attr-dirty') || element.is('html-attr-dirty'))
1134
1187
  ? element.parentNode.parentNode
1135
- : element, { type } = node, refName = getRefName(node), refGroup = getRefGroup(node);
1136
- if (!refName && !refGroup && !referenceTypes.has(type)) {
1137
- return undefined;
1138
- }
1139
- const name = getName(node), refs = root.querySelectorAll(type === 'heading-title' ? 'heading' : type).filter(token => type === 'attr-value'
1140
- ? getRefName(token) === refName || getRefGroup(token) === refGroup
1141
- : getName(token) === name).reverse().map((token) => ({
1188
+ : element;
1189
+ return getMatchingTokens(root, node, referenceTypes)?.map((token) => ({
1142
1190
  range: createNodeRange(token.type === 'parameter-key' ? token.parentNode : token),
1143
1191
  }));
1144
- return refs.length === 0 ? undefined : refs;
1192
+ }
1193
+ /**
1194
+ * 获取根节点和当前节点
1195
+ * @param text 源代码
1196
+ * @param position 位置
1197
+ */
1198
+ async #getRootAndNode(text, position) {
1199
+ const root = await this.#queue(text), node = root.elementFromPoint(position.character, position.line);
1200
+ return [root, node];
1145
1201
  }
1146
1202
  /**
1147
1203
  * Provide definitions
@@ -1151,7 +1207,7 @@ class LanguageService {
1151
1207
  * @param position position / 位置
1152
1208
  */
1153
1209
  async provideDefinition(text, position) {
1154
- const root = await this.#queue(text), node = root.elementFromPoint(position.character, position.line);
1210
+ const [root, node] = await this.#getRootAndNode(text, position);
1155
1211
  if (!node) {
1156
1212
  return undefined;
1157
1213
  }
@@ -1176,7 +1232,7 @@ class LanguageService {
1176
1232
  * @param position position / 位置
1177
1233
  */
1178
1234
  async resolveRenameLocation(text, position) {
1179
- const root = await this.#queue(text), node = root.elementFromPoint(position.character, position.line);
1235
+ const [, node] = await this.#getRootAndNode(text, position);
1180
1236
  if (!node) {
1181
1237
  return undefined;
1182
1238
  }
@@ -1196,7 +1252,7 @@ class LanguageService {
1196
1252
  * @param newName new name / 新名称
1197
1253
  */
1198
1254
  async provideRenameEdits(text, position, newName) {
1199
- const root = await this.#queue(text), node = root.elementFromPoint(position.character, position.line);
1255
+ const [root, node] = await this.#getRootAndNode(text, position);
1200
1256
  if (!node) {
1201
1257
  return undefined;
1202
1258
  }
@@ -4,6 +4,7 @@ export interface ParamLineParentBase {
4
4
  * Get all parameter keys
5
5
  *
6
6
  * 获取所有参数键
7
+ * @since v1.45.0
7
8
  */
8
9
  getKeys(): Set<string>;
9
10
  /**
@@ -11,12 +12,14 @@ export interface ParamLineParentBase {
11
12
  *
12
13
  * 获取指定参数
13
14
  * @param key parameter key / 参数键
15
+ * @since v1.45.0
14
16
  */
15
17
  getParams(key: string): ParamLineToken[];
16
18
  /**
17
19
  * Get duplicated parameters
18
20
  *
19
21
  * 获取重复参数
22
+ * @since v1.45.0
20
23
  */
21
24
  getDuplicatedParams(): [string, ParamLineToken[]][];
22
25
  }
@@ -30,8 +30,8 @@ const paramLineParent = (constructor) => {
30
30
  if (s) {
31
31
  const msg = index_1.default.msg('duplicate-ext-parameter', name), rect = new rect_1.BoundingRect(this, start), { computeEditInfo } = lintConfig;
32
32
  for (const [, params] of this.getDuplicatedParams()) {
33
- const duplication = Array.from({ length: params.length });
34
- for (let i = params.length - 1; i >= 0; i--) {
33
+ const { length } = params, duplication = Array.from({ length });
34
+ for (let i = length - 1; i >= 0; i--) {
35
35
  const param = params[i], e = (0, lint_1.generateForChild)(param, rect, rule, msg, s);
36
36
  if (computeEditInfo) {
37
37
  e.suggestions = [(0, lint_1.fixByRemove)(e, param === firstChild ? 0 : -1)];
@@ -159,7 +159,7 @@ class AttributesToken extends index_2.Token {
159
159
  /** @private */
160
160
  lint(start = this.getAbsoluteIndex(), re) {
161
161
  LINT: {
162
- const errors = super.lint(start, re), { parentNode, childNodes, type, name: tag } = this, attrs = new Map(), duplicated = new Set(), rect = new rect_1.BoundingRect(this, start), rules = ['no-ignored', 'required-attr', 'invalid-ref', 'no-duplicate'], { lintConfig } = index_1.default, { computeEditInfo, fix } = lintConfig, s = ['closingTag', 'invalidAttributes', 'nonWordAttributes']
162
+ const errors = super.lint(start, re), { parentNode, childNodes, length: l, type, name: tag } = this, attrs = new Map(), duplicated = new Set(), rect = new rect_1.BoundingRect(this, start), rules = ['no-ignored', 'required-attr', 'invalid-ref', 'no-duplicate'], { lintConfig } = index_1.default, { computeEditInfo, fix } = lintConfig, s = ['closingTag', 'invalidAttributes', 'nonWordAttributes']
163
163
  .map(k => lintConfig.getSeverity(rules[0], k));
164
164
  if (s[0] && this.#lint()) {
165
165
  const e = (0, lint_1.generateForSelf)(this, rect, rules[0], 'attributes-of-closing-tag', s[0]);
@@ -172,7 +172,8 @@ class AttributesToken extends index_2.Token {
172
172
  }
173
173
  errors.push(e);
174
174
  }
175
- for (const attr of childNodes) {
175
+ for (let i = l - 1; i >= 0; i--) {
176
+ const attr = childNodes[i];
176
177
  if (attr instanceof attribute_1.AttributeToken) {
177
178
  const { name } = attr;
178
179
  if (attrs.has(name)) {
@@ -217,8 +218,8 @@ class AttributesToken extends index_2.Token {
217
218
  const severity = lintConfig.getSeverity(rules[3], 'attribute');
218
219
  if (severity && duplicated.size > 0) {
219
220
  for (const key of duplicated) {
220
- const pairs = attrs.get(key).map(attr => [attr, attr.getValue()]), duplication = Array.from({ length: pairs.length }), values = new Set();
221
- for (let i = pairs.length - 1; i >= 0; i--) {
221
+ const pairs = attrs.get(key).map(attr => [attr, attr.getValue()]), { length } = pairs, duplication = Array.from({ length }), values = new Set();
222
+ for (let i = 0; i < length; i++) {
222
223
  const [attr, value] = pairs[i], e = (0, lint_1.generateForChild)(attr, rect, rules[3], index_1.default.msg('duplicate-attribute', key), severity);
223
224
  if (computeEditInfo || fix) {
224
225
  const remove = (0, lint_1.fixByRemove)(e);
@@ -232,7 +233,7 @@ class AttributesToken extends index_2.Token {
232
233
  }
233
234
  }
234
235
  }
235
- duplication[i] = e;
236
+ duplication[length - i - 1] = e;
236
237
  }
237
238
  Array.prototype.push.apply(errors, duplication);
238
239
  }
@@ -100,11 +100,7 @@ let ConverterFlagsToken = (() => {
100
100
  * 获取未知的转换类型标记
101
101
  */
102
102
  getUnknownFlags() {
103
- return new Set(this.#flags.filter(flag => /\{{3}[^{}]+\}{3}/u.test(flag)));
104
- }
105
- /** 从解析设置中获取语言变体 */
106
- #getVariants() {
107
- return new Set((0, string_1.toLowerCase)(this.getAttribute('config').variants));
103
+ LINT: return new Set(this.#flags.filter(flag => /\{{3}[^{}]+\}{3}/u.test(flag)));
108
104
  }
109
105
  /**
110
106
  * Get language coversion flags that specify a language variant
@@ -112,16 +108,10 @@ let ConverterFlagsToken = (() => {
112
108
  * 获取指定语言变体的转换标记
113
109
  */
114
110
  getVariantFlags() {
115
- const variants = this.#getVariants();
116
- return new Set((0, string_1.toLowerCase)(this.#flags).filter(flag => variants.has(flag)));
117
- }
118
- /** @private */
119
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
120
- isInvalidFlag(flag, variant, unknown) {
121
- return Boolean(flag)
122
- && !variant.has(flag.toLowerCase())
123
- && !unknown.has(flag)
124
- && (variant.size > 0 || !definedFlags.has(flag));
111
+ LINT: {
112
+ const variants = new Set((0, string_1.toLowerCase)(this.getAttribute('config').variants));
113
+ return new Set((0, string_1.toLowerCase)(this.#flags).filter(flag => variants.has(flag)));
114
+ }
125
115
  }
126
116
  /** @private */
127
117
  lint(start = this.getAbsoluteIndex(), re) {
@@ -134,9 +124,12 @@ let ConverterFlagsToken = (() => {
134
124
  const rule = 'no-ignored', s = lintConfig.getSeverity(rule, 'conversionFlag');
135
125
  if (s) {
136
126
  const rect = new rect_1.BoundingRect(this, start), { childNodes, length } = this;
137
- for (let i = 0; i < length; i++) {
127
+ for (let i = length - 1; i >= 0; i--) {
138
128
  const child = childNodes[i], flag = child.text().trim();
139
- if (this.isInvalidFlag(flag, variantFlags, unknownFlags)) {
129
+ if (flag
130
+ && !variantFlags.has(flag.toLowerCase())
131
+ && !unknownFlags.has(flag)
132
+ && (variantFlags.size > 0 || !definedFlags.has(flag))) {
140
133
  const e = (0, lint_1.generateForChild)(child, rect, rule, 'invalid-conversion-flag', s);
141
134
  if (computeEditInfo || fix) {
142
135
  if (variantFlags.size === 0 && definedFlags.has(flag.toUpperCase())) {
@@ -111,8 +111,9 @@ class FileToken extends base_1.LinkBaseToken {
111
111
  }
112
112
  s = lintConfig.getSeverity(rule, 'parameter');
113
113
  if (s && unscaled) {
114
- for (const arg of args.filter(({ name }) => name === 'width')) {
115
- const e = (0, lint_1.generateForChild)(arg, rect, rule, 'invalid-image-parameter', s);
114
+ const widths = args.filter(({ name }) => name === 'width');
115
+ for (let i = widths.length - 1; i >= 0; i--) {
116
+ const e = (0, lint_1.generateForChild)(widths[i], rect, rule, 'invalid-image-parameter', s);
116
117
  if (computeEditInfo || fix) {
117
118
  e.fix = (0, lint_1.fixByRemove)(e, -1);
118
119
  }
@@ -147,8 +148,8 @@ class FileToken extends base_1.LinkBaseToken {
147
148
  * @param severity 错误等级
148
149
  */
149
150
  const generate = (tokens, msg, p1, severity = true) => {
150
- const duplication = Array.from({ length: tokens.length });
151
- for (let i = tokens.length - 1; i >= 0; i--) {
151
+ const { length } = tokens, duplication = Array.from({ length });
152
+ for (let i = length - 1; i >= 0; i--) {
152
153
  const arg = tokens[i];
153
154
  s = severities[Number(typeof severity === 'function' ? severity(arg) : severity)];
154
155
  if (!s) {
@@ -30,21 +30,21 @@ class CharinsertToken extends index_2.MultiLineToken {
30
30
  /** @private */
31
31
  lint(start = this.getAbsoluteIndex()) {
32
32
  LINT: {
33
- const { name, childNodes } = this, rule = 'no-ignored', { lintConfig } = index_1.default, { computeEditInfo } = lintConfig, s = lintConfig.getSeverity(rule, name);
33
+ const { name, childNodes, length: l } = this, rule = 'no-ignored', { lintConfig } = index_1.default, { computeEditInfo } = lintConfig, s = lintConfig.getSeverity(rule, name);
34
34
  if (!s) {
35
35
  return [];
36
36
  }
37
- const rect = new rect_1.BoundingRect(this, start), msg = index_1.default.msg('invalid-space', name), trailing = [];
38
- let errors = super.lint(start), begin = false;
39
- for (const child of childNodes) {
40
- const str = String(child), trimmed = str.trim();
37
+ const rect = new rect_1.BoundingRect(this, start), msg = index_1.default.msg('invalid-space', name), leading = [], errors = super.lint(start);
38
+ let begin = false;
39
+ for (let i = l - 1; i >= 0; i--) {
40
+ const child = childNodes[i], str = child.toString(), trimmed = str.trim();
41
41
  if (trimmed) {
42
42
  if (begin) {
43
- const n = (0, common_1.numLeadingSpaces)(str);
44
- if (n) {
43
+ const [, { length }] = /(?:^|\S)(\s*)$/u.exec(str);
44
+ if (length) {
45
45
  const e = (0, lint_1.generateForChild)(child, rect, rule, msg, s);
46
- e.endIndex = e.startIndex + n;
47
- e.endCol = e.startCol + n;
46
+ e.startIndex = e.endIndex - length;
47
+ e.startCol = e.endCol - length;
48
48
  if (computeEditInfo) {
49
49
  e.fix = (0, lint_1.fixByRemove)(e);
50
50
  }
@@ -54,21 +54,21 @@ class CharinsertToken extends index_2.MultiLineToken {
54
54
  else {
55
55
  begin = true;
56
56
  }
57
- if (trailing.length > 0) {
58
- errors = [...errors, ...trailing];
59
- trailing.length = 0;
57
+ if (leading.length > 0) {
58
+ Array.prototype.push.apply(errors, leading);
59
+ leading.length = 0;
60
60
  }
61
61
  }
62
62
  if (begin) {
63
- const [, { length }] = /(?:^|\S)(\s*)$/u.exec(str);
64
- if (length) {
63
+ const n = (0, common_1.numLeadingSpaces)(str);
64
+ if (n) {
65
65
  const e = (0, lint_1.generateForChild)(child, rect, rule, msg, s);
66
- e.startIndex = e.endIndex - length;
67
- e.startCol = e.endCol - length;
66
+ e.endIndex = e.startIndex + n;
67
+ e.endCol = e.startCol + n;
68
68
  if (computeEditInfo) {
69
69
  e.fix = (0, lint_1.fixByRemove)(e);
70
70
  }
71
- trailing.push(e);
71
+ leading.push(e);
72
72
  }
73
73
  }
74
74
  }
@@ -83,20 +83,21 @@ class ImagemapToken extends index_2.MultiLineToken {
83
83
  const errors = super.lint(start, re), rect = new rect_1.BoundingRect(this, start), { childNodes, image } = this, rule = 'invalid-imagemap', { lintConfig } = index_1.default, { computeEditInfo } = lintConfig, s = lintConfig.getSeverity(rule, image ? 'link' : 'image');
84
84
  if (s) {
85
85
  if (image) {
86
- Array.prototype.push.apply(errors, childNodes.filter(child => {
86
+ const comments = childNodes.filter(child => {
87
87
  const str = child.toString().trim();
88
88
  return child.is('noinclude')
89
89
  && str && !str.startsWith('#');
90
- }).map(child => {
91
- const e = (0, lint_1.generateForChild)(child, rect, rule, 'invalid-imagemap-link', s);
90
+ });
91
+ for (let i = comments.length - 1; i >= 0; i--) {
92
+ const e = (0, lint_1.generateForChild)(comments[i], rect, rule, 'invalid-imagemap-link', s);
92
93
  if (computeEditInfo) {
93
94
  e.suggestions = [
94
95
  (0, lint_1.fixByRemove)(e, -1),
95
96
  (0, lint_1.fixBy)(e, 'comment', '# '),
96
97
  ];
97
98
  }
98
- return e;
99
- }));
99
+ errors.push(e);
100
+ }
100
101
  }
101
102
  else {
102
103
  errors.push((0, lint_1.generateForSelf)(this, rect, rule, 'imagemap-without-image', s));
@@ -64,30 +64,28 @@ class NestedToken extends index_2.Token {
64
64
  if (!s) {
65
65
  return errors;
66
66
  }
67
- const rect = new rect_1.BoundingRect(this, start), msg = index_1.default.msg('invalid-content', name), regex = typeof this.#regex === 'boolean' ? lintRegex[this.#regex ? 1 : 0] : /^<!--[\s\S]*-->$/u;
68
- return [
69
- ...errors,
70
- ...childNodes.filter(child => {
71
- const { type, name: n } = child;
72
- if (type === 'ext') {
73
- return !this.#tags.includes(n);
74
- }
75
- else if (childTypes.has(type)) {
76
- return false;
77
- }
78
- const str = child.toString().trim();
79
- return str && !regex.test(str);
80
- }).map(child => {
81
- const e = (0, lint_1.generateForChild)(child, rect, rule, msg, s);
82
- if (computeEditInfo) {
83
- e.suggestions = [
84
- (0, lint_1.fixByRemove)(e),
85
- (0, lint_1.fixByComment)(e, child.toString()),
86
- ];
87
- }
88
- return e;
89
- }),
90
- ];
67
+ const rect = new rect_1.BoundingRect(this, start), msg = index_1.default.msg('invalid-content', name), regex = typeof this.#regex === 'boolean' ? lintRegex[this.#regex ? 1 : 0] : /^<!--[\s\S]*-->$/u, ignored = childNodes.filter(child => {
68
+ const { type, name: n } = child;
69
+ if (type === 'ext') {
70
+ return !this.#tags.includes(n);
71
+ }
72
+ else if (childTypes.has(type)) {
73
+ return false;
74
+ }
75
+ const str = child.toString().trim();
76
+ return str && !regex.test(str);
77
+ }), { length } = ignored, ignoredErrors = Array.from({ length });
78
+ for (let i = length - 1; i >= 0; i--) {
79
+ const child = ignored[i], e = (0, lint_1.generateForChild)(child, rect, rule, msg, s);
80
+ if (computeEditInfo) {
81
+ e.suggestions = [
82
+ (0, lint_1.fixByRemove)(e),
83
+ (0, lint_1.fixByComment)(e, child.toString()),
84
+ ];
85
+ }
86
+ ignoredErrors[i] = e;
87
+ }
88
+ return [...errors, ...ignoredErrors];
91
89
  }
92
90
  }
93
91
  }
@@ -17,7 +17,12 @@ export declare abstract class ParamLineToken extends Token {
17
17
  abstract get nextSibling(): this | undefined;
18
18
  abstract get previousSibling(): this | undefined;
19
19
  get type(): 'param-line';
20
- /** A reasonable parameter name / 合理的参数名 */
20
+ /**
21
+ * A reasonable parameter name
22
+ *
23
+ * 合理的参数名
24
+ * @since v1.45.0
25
+ */
21
26
  get key(): string | undefined;
22
27
  /** @param name 扩展标签名 */
23
28
  constructor(name: string, wikitext: string, config: Config, accum: Token[], acceptable?: WikiParserAcceptable);
@@ -25,6 +30,7 @@ export declare abstract class ParamLineToken extends Token {
25
30
  * Get a reasonable parameter name
26
31
  *
27
32
  * 获取合理的参数名
33
+ * @since v1.45.0
28
34
  */
29
35
  getKey(): string | undefined;
30
36
  }
@@ -69,7 +69,12 @@ let ParamLineToken = (() => {
69
69
  get type() {
70
70
  return 'param-line';
71
71
  }
72
- /** A reasonable parameter name / 合理的参数名 */
72
+ /**
73
+ * A reasonable parameter name
74
+ *
75
+ * 合理的参数名
76
+ * @since v1.45.0
77
+ */
73
78
  get key() {
74
79
  LINT: return this.getKey();
75
80
  }
@@ -131,6 +136,7 @@ let ParamLineToken = (() => {
131
136
  * Get a reasonable parameter name
132
137
  *
133
138
  * 获取合理的参数名
139
+ * @since v1.45.0
134
140
  */
135
141
  getKey() {
136
142
  LINT: {
@@ -148,8 +148,9 @@ let TdToken = (() => {
148
148
  /** @private */
149
149
  lint(start = this.getAbsoluteIndex(), re) {
150
150
  LINT: {
151
- const errors = super.lint(start, re), rect = new rect_1.BoundingRect(this, start + this.getRelativeIndex(this.length - 1)), rule = 'pipe-like', { lintConfig } = index_1.default, { computeEditInfo, fix } = lintConfig, severities = ['td', 'double'].map(key => lintConfig.getSeverity(rule, key));
152
- for (const child of this.lastChild.childNodes) {
151
+ const errors = super.lint(start, re), rect = new rect_1.BoundingRect(this, start + this.getRelativeIndex(this.length - 1)), rule = 'pipe-like', { lintConfig } = index_1.default, { computeEditInfo, fix } = lintConfig, severities = ['td', 'double'].map(key => lintConfig.getSeverity(rule, key)), { childNodes, length } = this.lastChild, pipes = [];
152
+ for (let i = length - 1; i >= 0; i--) {
153
+ const child = childNodes[i];
153
154
  if (child.type === 'text') {
154
155
  const { data } = child;
155
156
  if (data.includes('|')) {
@@ -165,12 +166,12 @@ let TdToken = (() => {
165
166
  e.suggestions = [(0, lint_1.fixByPipe)(e.startIndex, data)];
166
167
  }
167
168
  }
168
- errors.push(e);
169
+ pipes.push(e);
169
170
  }
170
171
  }
171
172
  }
172
173
  }
173
- return errors;
174
+ return [...errors, ...pipes.reverse()];
174
175
  }
175
176
  }
176
177
  /**
@@ -62,11 +62,16 @@ class TagToken extends index_1.Token {
62
62
  return undefined;
63
63
  }
64
64
  const { childNodes } = parentNode, i = childNodes.indexOf(this), siblings = closing ? childNodes.slice(0, i) : childNodes.slice(i + 1).reverse(), stack = [this], { rev } = debug_1.Shadow;
65
- let cur = siblings.pop();
65
+ let cur = siblings.pop(), until;
66
66
  while (cur) {
67
67
  // eslint-disable-next-line no-empty, @typescript-eslint/no-unnecessary-condition
68
68
  if (!cur) {
69
69
  }
70
+ else if (until) {
71
+ if (cur === until) {
72
+ until = undefined;
73
+ }
74
+ }
70
75
  else if (!cur.is(type)
71
76
  || type === 'html' && (cur.name !== name || isFlexible && cur.selfClosing)) {
72
77
  //
@@ -76,6 +81,13 @@ class TagToken extends index_1.Token {
76
81
  if (type === 'tvar') {
77
82
  return undefined;
78
83
  }
84
+ else if (cur.#match?.[0] === rev) {
85
+ const [, matched] = cur.#match;
86
+ if (!matched) {
87
+ return undefined;
88
+ }
89
+ until = matched;
90
+ }
79
91
  stack.push(cur);
80
92
  }
81
93
  else {
@@ -278,7 +278,7 @@ let TranscludeToken = (() => {
278
278
  if (!this.isTemplate()) {
279
279
  return errors;
280
280
  }
281
- const { type, childNodes, length } = this, rect = new rect_1.BoundingRect(this, start), { lintConfig } = index_1.default, { computeEditInfo } = lintConfig, invoke = type === 'magic-word';
281
+ const { type, childNodes, length: l } = this, rect = new rect_1.BoundingRect(this, start), { lintConfig } = index_1.default, { computeEditInfo } = lintConfig, invoke = type === 'magic-word';
282
282
  let rule = 'no-ignored', s = lintConfig.getSeverity(rule, 'fragment');
283
283
  if (invoke && !this.#getTitle().valid) {
284
284
  rule = 'invalid-invoke';
@@ -302,7 +302,7 @@ let TranscludeToken = (() => {
302
302
  }
303
303
  rule = 'invalid-invoke';
304
304
  s = lintConfig.getSeverity(rule, 'function');
305
- if (s && invoke && length === 2) {
305
+ if (s && invoke && l === 2) {
306
306
  errors.push((0, lint_1.generateForSelf)(this, rect, rule, 'missing-function', s));
307
307
  return errors;
308
308
  }
@@ -312,8 +312,8 @@ let TranscludeToken = (() => {
312
312
  const duplicatedArgs = this.getDuplicatedArgs()
313
313
  .filter(([, parameter]) => !parameter[0].querySelector('ext'));
314
314
  for (const [, args] of duplicatedArgs) {
315
- const duplication = Array.from({ length: args.length });
316
- for (let i = args.length - 1; i >= 0; i--) {
315
+ const { length } = args, duplication = Array.from({ length });
316
+ for (let i = length - 1; i >= 0; i--) {
317
317
  const arg = args[i], e = (0, lint_1.generateForChild)(arg, rect, rule, 'duplicate-parameter', s);
318
318
  if (computeEditInfo) {
319
319
  e.suggestions = [(0, lint_1.fixByRemove)(e, -1)];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikilint",
3
- "version": "2.45.0",
3
+ "version": "2.46.0",
4
4
  "description": "A Node.js linter for MediaWiki markup",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -42,19 +42,19 @@
42
42
  "ls": "npm i --package-lock-only && npm ls --package-lock-only --all --include=peer --omit=dev",
43
43
  "prepublishOnly": "npm run build:core",
44
44
  "build:core": "bash build.sh",
45
- "build:define": "bash sed.sh -i \"s/pkg = \\$PKG/pkg = $(npm pkg get name)/; s/version = \\$VERSION/version = $(npm pkg get version)/\" dist/bin/config.js",
45
+ "build:define": "bash sed.sh -i \"s/pkg = \\$PKG/pkg = '$(npm pkg get name)'/; s/version = \\$VERSION/version = '$(npm pkg get version)'/\" dist/bin/config.js",
46
46
  "build": "npm run build:core",
47
47
  "diff": "bash diff.sh",
48
48
  "diff:stat": "f() { git diff --stat -w --color=always $1 $2 -- . | grep '\\.ts'; }; f",
49
49
  "lint:ts": "tsc --noEmit && eslint --cache .",
50
- "lint:json": "v8r -s config/.schema.json config/*.json && v8r -s data/.schema.json data/*.json && mocha dist/test/json.js",
50
+ "lint:json": "v8r -s config/.schema.json config/*.json && v8r -s data/.schema.json data/*.json && testUtil dist/test/json.js",
51
51
  "lint:md": "markdownlint-cli2 '*.md'",
52
52
  "lint": "npm run lint:ts && npm run lint:json && npm run lint:md",
53
53
  "prof": "node dist/test/prof.js",
54
54
  "coverage": "tsc --declaration false --target es2024 && npm run build:define && c8 npm run test:ci && node dist/script/coverage.js && open coverage/index.html",
55
- "test:unit": "mocha dist/test/test.js",
56
- "test:parser": "LC_ALL=en.UTF-8 mocha dist/test/parserTests.js",
57
- "test:perf": "mocha --reporter spec dist/test/perf.js",
55
+ "test:unit": "testUtil dist/test/test.js",
56
+ "test:parser": "LC_ALL=en.UTF-8 testUtil dist/test/parserTests.js",
57
+ "test:perf": "testUtil --test-reporter=spec dist/test/perf.js",
58
58
  "test": "npm run test:unit && npm run test:parser",
59
59
  "test:parseronly": "LSP=0 npm run test:parser",
60
60
  "test:ci": "LSP=0 npm test",
@@ -98,23 +98,23 @@
98
98
  }
99
99
  },
100
100
  "dependencies": {
101
- "@bhsd/cm-util": "^2.2.0",
102
- "@bhsd/common": "^3.0.0",
101
+ "@bhsd/cm-util": "^2.3.0",
102
+ "@bhsd/common": "^4.0.0",
103
103
  "@bhsd/stylelint-util": "^1.1.0",
104
104
  "entities": "^8.0.0",
105
105
  "vscode-languageserver-types": "^3.18.0"
106
106
  },
107
107
  "devDependencies": {
108
108
  "@bhsd/code-standard": "^3.1.0",
109
- "@bhsd/nodejs": "^1.1.0",
110
- "@bhsd/test-util": "^2.0.0",
109
+ "@bhsd/nodejs": "^1.2.1",
110
+ "@bhsd/test-util": "^3.1.0",
111
111
  "c8": "^11.0.0",
112
+ "culori": "^4.0.2",
112
113
  "diff2html-cli": "^5.2.15",
113
114
  "esbuild": "^0.28.1",
114
- "eslint": "^10.6.0",
115
+ "eslint": "^10.7.0",
115
116
  "markdownlint-cli2": "^0.23.0",
116
117
  "mathoid-texvcjs": "^0.6.0",
117
- "mocha": "^11.7.6",
118
118
  "stylelint": "^17.14.0",
119
119
  "typescript": "^6.0.3",
120
120
  "v8r": "^6.1.0",