wikilint 2.5.7 → 2.6.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.
@@ -0,0 +1,14 @@
1
+ export interface AttributesParentBase {
2
+ /**
3
+ * 获取AttributesToken子节点的属性
4
+ * @param key 属性键
5
+ */
6
+ getAttr(key: string): string | true | undefined;
7
+ }
8
+ /**
9
+ * 子节点含有AttributesToken的类
10
+ * @param i AttributesToken子节点的位置
11
+ * @param constructor 基类
12
+ * @param _ context
13
+ */
14
+ export declare const attributesParent: (i?: number) => <T extends AstConstructor>(constructor: T, _?: unknown) => T;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.attributesParent = void 0;
4
+ /**
5
+ * 子节点含有AttributesToken的类
6
+ * @param i AttributesToken子节点的位置
7
+ * @param constructor 基类
8
+ * @param _ context
9
+ */
10
+ const attributesParent = (i = 0) => (constructor, _) => {
11
+ /** 子节点含有AttributesToken的类 */
12
+ class AttributesParent extends constructor {
13
+ /** AttributesToken子节点 */
14
+ get #attributesChild() {
15
+ return this.childNodes[i];
16
+ }
17
+ /** @implements */
18
+ getAttr(key) {
19
+ return this.#attributesChild.getAttr(key);
20
+ }
21
+ }
22
+ Object.defineProperty(AttributesParent, 'name', { value: constructor.name });
23
+ return AttributesParent;
24
+ };
25
+ exports.attributesParent = attributesParent;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 解析后不可见的类
3
+ * @param constructor 基类
4
+ */
5
+ export declare const hiddenToken: <T extends AstConstructor>(constructor: T) => T;
@@ -2,6 +2,7 @@ import { BuildMethod } from '../util/constants';
2
2
  import Parser from '../index';
3
3
  import { AstElement } from '../lib/element';
4
4
  import { AstText } from '../lib/text';
5
+ import type { LintError } from '../base';
5
6
  import type { Title } from '../lib/title';
6
7
  import type { AstNodes } from '../internal';
7
8
  import type { TokenTypes } from '../util/constants';
@@ -30,4 +31,6 @@ export declare class Token extends AstElement {
30
31
  * @param selfLink 是否允许selfLink
31
32
  */
32
33
  normalizeTitle(title: string, defaultNs?: number, halfParsed?: boolean, decode?: boolean, selfLink?: boolean): Title;
34
+ /** @override */
35
+ lint(start?: number, re?: RegExp): LintError[];
33
36
  }
package/dist/src/index.js CHANGED
@@ -41,6 +41,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
41
41
  exports.Token = void 0;
42
42
  const string_1 = require("../util/string");
43
43
  const constants_1 = require("../util/constants");
44
+ const lint_1 = require("../util/lint");
44
45
  const index_1 = require("../index");
45
46
  const element_1 = require("../lib/element");
46
47
  const text_1 = require("../lib/text");
@@ -317,5 +318,74 @@ class Token extends element_1.AstElement {
317
318
  normalizeTitle(title, defaultNs = 0, halfParsed = false, decode = false, selfLink = false) {
318
319
  return index_1.default.normalizeTitle(title, defaultNs, this.#include, this.#config, halfParsed, decode, selfLink);
319
320
  }
321
+ /** @override */
322
+ lint(start = this.getAbsoluteIndex(), re) {
323
+ const errors = super.lint(start, re);
324
+ const record = {};
325
+ for (const cat of this.childNodes.filter((node) => node.type === 'category')) {
326
+ const thisCat = record[cat.name];
327
+ if (thisCat) {
328
+ thisCat.add(cat);
329
+ }
330
+ else {
331
+ record[cat.name] = new Set([cat]);
332
+ }
333
+ }
334
+ for (const value of Object.values(record)) {
335
+ if (value.size > 1) {
336
+ errors.push(...[...value].map(cat => {
337
+ const e = (0, lint_1.generateForSelf)(cat, { start: cat.getAbsoluteIndex() }, 'no-duplicate', 'duplicated category');
338
+ e.suggestions = [
339
+ {
340
+ desc: 'remove',
341
+ range: [e.startIndex, e.endIndex],
342
+ text: '',
343
+ },
344
+ ];
345
+ return e;
346
+ }));
347
+ }
348
+ }
349
+ if (this.type === 'root') {
350
+ const regex = /<!--\s*lint-(disable(?:(?:-next)?-line)?|enable)(\s[\sa-z,-]*)?-->/gu, wikitext = String(this), ignores = [];
351
+ let mt = regex.exec(wikitext), last = 0, curLine = 0;
352
+ while (mt) {
353
+ const { 1: type, index } = mt, detail = mt[2]?.trim();
354
+ curLine += wikitext.slice(last, index).split('\n').length - 1;
355
+ last = index;
356
+ ignores.push({
357
+ line: curLine + (type === 'disable-line' ? 0 : 1),
358
+ from: type === 'disable' ? regex.lastIndex : undefined,
359
+ to: type === 'enable' ? regex.lastIndex : undefined,
360
+ rules: detail ? new Set(detail.split(',').map(r => r.trim())) : undefined,
361
+ });
362
+ mt = regex.exec(wikitext);
363
+ }
364
+ return errors.filter(({ rule, startLine, startIndex }) => {
365
+ const nearest = { pos: 0 };
366
+ for (const { line, from, to, rules } of ignores) {
367
+ if (line > startLine + 1) {
368
+ break;
369
+ }
370
+ else if (rules && !rules.has(rule)) {
371
+ continue;
372
+ }
373
+ else if (line === startLine && from === undefined && to === undefined) {
374
+ return false;
375
+ }
376
+ else if (from <= startIndex && from > nearest.pos) {
377
+ nearest.pos = from;
378
+ nearest.type = 'from';
379
+ }
380
+ else if (to <= startIndex && to > nearest.pos) {
381
+ nearest.pos = to;
382
+ nearest.type = 'to';
383
+ }
384
+ }
385
+ return nearest.type !== 'from';
386
+ });
387
+ }
388
+ return errors;
389
+ }
320
390
  }
321
391
  exports.Token = Token;
@@ -10,6 +10,7 @@ import type { Title } from '../../lib/title';
10
10
  export declare abstract class LinkBaseToken extends Token {
11
11
  #private;
12
12
  type: 'link' | 'category' | 'file' | 'gallery-image' | 'imagemap-image';
13
+ readonly name: string;
13
14
  readonly childNodes: readonly [AtomToken, ...Token[]];
14
15
  abstract get firstChild(): AtomToken;
15
16
  abstract get lastChild(): Token;
@@ -36,6 +36,7 @@ class LinkBaseToken extends index_2.Token {
36
36
  if (this.#delimiter.includes('\0')) {
37
37
  this.#delimiter = this.buildFromStr(this.#delimiter, constants_1.BuildMethod.String);
38
38
  }
39
+ this.setAttribute('name', this.#title.main);
39
40
  }
40
41
  /** @private */
41
42
  setAttribute(key, value) {
@@ -2,10 +2,9 @@ import Parser from '../../index';
2
2
  import { Token } from '../index';
3
3
  import { SyntaxToken } from '../syntax';
4
4
  import { AttributesToken } from '../attributes';
5
- /**
6
- * 表格行,含开头的换行,不含结尾的换行
7
- * @classdesc `{childNodes: [SyntaxToken, AttributesToken, ...Token]}`
8
- */
5
+ import type { AttributesParentBase } from '../../mixin/attributesParent';
6
+ export interface TableBaseToken extends AttributesParentBase {
7
+ }
9
8
  export declare abstract class TableBaseToken extends Token {
10
9
  type: 'table' | 'tr' | 'td';
11
10
  readonly childNodes: readonly [SyntaxToken, AttributesToken, ...Token[]];
@@ -18,3 +17,4 @@ export declare abstract class TableBaseToken extends Token {
18
17
  */
19
18
  constructor(pattern: RegExp, syntax?: string, attr?: string, config?: Parser.Config, accum?: Token[], acceptable?: Acceptable);
20
19
  }
20
+ export {};
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TableBaseToken = void 0;
4
+ const attributesParent_1 = require("../../mixin/attributesParent");
4
5
  const index_1 = require("../../index");
5
6
  const index_2 = require("../index");
6
7
  const syntax_1 = require("../syntax");
@@ -9,7 +10,7 @@ const attributes_1 = require("../attributes");
9
10
  * 表格行,含开头的换行,不含结尾的换行
10
11
  * @classdesc `{childNodes: [SyntaxToken, AttributesToken, ...Token]}`
11
12
  */
12
- class TableBaseToken extends index_2.Token {
13
+ class TableBaseToken extends (0, attributesParent_1.attributesParent)(1)(index_2.Token) {
13
14
  /**
14
15
  * @param pattern 表格语法正则
15
16
  * @param syntax 表格语法
@@ -3,6 +3,15 @@ import { TrBaseToken } from './trBase';
3
3
  import { SyntaxToken } from '../syntax';
4
4
  import type { LintError } from '../../base';
5
5
  import type { AttributesToken, TdToken, TrToken, Token } from '../../internal';
6
+ import type { TableCoords } from './trBase';
7
+ /**
8
+ * 是否是行尾
9
+ * @param {Token} cell 表格单元格
10
+ */
11
+ export declare const isRowEnd: ({ type }: Token) => boolean;
12
+ /** @extends {Array<TableCoords[]>} */
13
+ export declare class Layout extends Array<TableCoords[]> {
14
+ }
6
15
  /**
7
16
  * 表格
8
17
  * @classdesc `{childNodes: [SyntaxToken, AttributesToken, ?Token, ...TdToken, ...TrToken, ?SyntaxToken]}`
@@ -20,4 +29,33 @@ export declare abstract class TableToken extends TrBaseToken {
20
29
  constructor(syntax: string, attr?: string, config?: Parser.Config, accum?: Token[]);
21
30
  /** @override */
22
31
  lint(start?: number, re?: RegExp): LintError[];
32
+ /**
33
+ * 获取表格布局
34
+ * @param stop 中止条件
35
+ * @param stop.row 中止行
36
+ * @param stop.column 中止列
37
+ * @param stop.x 中止行
38
+ * @param stop.y 中止列
39
+ */
40
+ getLayout(stop?: {
41
+ row?: number;
42
+ column?: number;
43
+ x?: number;
44
+ y?: number;
45
+ }): Layout;
46
+ /** 获取所有行 */
47
+ getAllRows(): (TrToken | this)[];
48
+ /**
49
+ * 获取指定坐标的单元格
50
+ * @param coords 表格坐标
51
+ */
52
+ getNthCell(coords: TableCoords): TdToken | undefined;
53
+ /**
54
+ * 获取第n行
55
+ * @param n 行号
56
+ * @param force 是否将表格自身视为第一行
57
+ * @param insert 是否用于判断插入新行的位置
58
+ * @throws `RangeError` 不存在该行
59
+ */
60
+ getNthRow(n: number, force?: boolean, insert?: false): TrToken | this | undefined;
23
61
  }
@@ -1,12 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TableToken = void 0;
3
+ exports.TableToken = exports.Layout = exports.isRowEnd = void 0;
4
4
  const lint_1 = require("../../util/lint");
5
5
  const debug_1 = require("../../util/debug");
6
6
  const index_1 = require("../../index");
7
7
  const trBase_1 = require("./trBase");
8
8
  const syntax_1 = require("../syntax");
9
9
  const closingPattern = /^\n[^\S\n]*(?:\|\}|\{\{\s*!\s*\}\}\}|\{\{\s*!\)\s*\}\})$/u;
10
+ /**
11
+ * 是否是行尾
12
+ * @param {Token} cell 表格单元格
13
+ */
14
+ const isRowEnd = ({ type }) => type === 'tr' || type === 'table-syntax';
15
+ exports.isRowEnd = isRowEnd;
16
+ /** @extends {Array<TableCoords[]>} */
17
+ class Layout extends Array {
18
+ }
19
+ exports.Layout = Layout;
10
20
  /**
11
21
  * 表格
12
22
  * @classdesc `{childNodes: [SyntaxToken, AttributesToken, ?Token, ...TdToken, ...TrToken, ?SyntaxToken]}`
@@ -30,6 +40,29 @@ class TableToken extends trBase_1.TrBaseToken {
30
40
  if (!this.closed) {
31
41
  errors.push((0, lint_1.generateForChild)(this.firstChild, { start }, 'unclosed-table', index_1.default.msg('unclosed $1', 'table')));
32
42
  }
43
+ const layout = this.getLayout(), { length } = layout;
44
+ if (length > 1) {
45
+ let low = 1, high = Infinity, j = 0;
46
+ for (; j < length; j++) {
47
+ const row = layout[j], max = row.length;
48
+ if (max < low) {
49
+ break;
50
+ }
51
+ else if (max < high) {
52
+ high = max;
53
+ }
54
+ const { colspan } = this.getNthCell(row[row.length - 1]), min = max - colspan + 1;
55
+ if (min > high) {
56
+ break;
57
+ }
58
+ else if (min > low) {
59
+ low = min;
60
+ }
61
+ }
62
+ if (j < length) {
63
+ errors.push((0, lint_1.generateForChild)(this.getNthRow(j), { start }, 'table-layout', 'inconsistent table layout', 'warning'));
64
+ }
65
+ }
33
66
  return errors;
34
67
  }
35
68
  /** @private */
@@ -41,5 +74,81 @@ class TableToken extends trBase_1.TrBaseToken {
41
74
  }
42
75
  this.lastChild.replaceChildren(...inner);
43
76
  }
77
+ /**
78
+ * 获取表格布局
79
+ * @param stop 中止条件
80
+ * @param stop.row 中止行
81
+ * @param stop.column 中止列
82
+ * @param stop.x 中止行
83
+ * @param stop.y 中止列
84
+ */
85
+ getLayout(stop) {
86
+ const rows = this.getAllRows(), { length } = rows, layout = new Layout(...(0, debug_1.emptyArray)(length, () => []));
87
+ for (let i = 0; i < length; i++) {
88
+ const rowLayout = layout[i];
89
+ let j = 0, k = 0, last;
90
+ for (const cell of rows[i].childNodes.slice(2)) {
91
+ if (cell.type === 'td') {
92
+ if (cell.isIndependent()) {
93
+ last = cell.subtype !== 'caption';
94
+ }
95
+ if (last) {
96
+ const coords = { row: i, column: j }, { rowspan, colspan } = cell;
97
+ j++;
98
+ while (rowLayout[k]) {
99
+ k++;
100
+ }
101
+ for (let y = i; y < Math.min(i + rowspan, length); y++) {
102
+ for (let x = k; x < k + colspan; x++) {
103
+ layout[y][x] = coords;
104
+ }
105
+ }
106
+ k += colspan;
107
+ }
108
+ }
109
+ else if ((0, exports.isRowEnd)(cell)) {
110
+ break;
111
+ }
112
+ }
113
+ }
114
+ return layout;
115
+ }
116
+ /** 获取所有行 */
117
+ getAllRows() {
118
+ return [
119
+ ...super.getRowCount() ? [this] : [],
120
+ ...this.childNodes.slice(1)
121
+ .filter((child) => child.type === 'tr' && child.getRowCount() > 0),
122
+ ];
123
+ }
124
+ /**
125
+ * 获取指定坐标的单元格
126
+ * @param coords 表格坐标
127
+ */
128
+ getNthCell(coords) {
129
+ const rawCoords = coords;
130
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
131
+ return rawCoords && this.getNthRow(rawCoords.row, false, false)?.getNthCol(rawCoords.column);
132
+ }
133
+ getNthRow(n, force = false, insert = false) {
134
+ const isRow = super.getRowCount();
135
+ if (n === 0
136
+ // eslint-disable-next-line @stylistic/no-extra-parens
137
+ && (isRow)) {
138
+ return this;
139
+ }
140
+ else if (isRow) {
141
+ n--;
142
+ }
143
+ for (const child of this.childNodes.slice(2)) {
144
+ if (child.type === 'tr' && child.getRowCount()) {
145
+ n--;
146
+ if (n < 0) {
147
+ return child;
148
+ }
149
+ }
150
+ }
151
+ return undefined;
152
+ }
44
153
  }
45
154
  exports.TableToken = TableToken;
@@ -8,7 +8,7 @@ export interface TdSpanAttrs {
8
8
  rowspan?: number;
9
9
  colspan?: number;
10
10
  }
11
- export type TdAttrs = Record<string, string | true> & TdSpanAttrs;
11
+ declare type TdAttrGetter<T extends string> = T extends keyof TdSpanAttrs ? number : string | true | undefined;
12
12
  /**
13
13
  * `<td>`、`<th>`和`<caption>`
14
14
  * @classdesc `{childNodes: [SyntaxToken, AttributesToken, Token]}`
@@ -20,6 +20,10 @@ export declare abstract class TdToken extends TableBaseToken {
20
20
  abstract get parentNode(): TrToken | TableToken | undefined;
21
21
  abstract get nextSibling(): this | TrToken | SyntaxToken | undefined;
22
22
  abstract get previousSibling(): Token | undefined;
23
+ /** rowspan */
24
+ get rowspan(): number;
25
+ /** colspan */
26
+ get colspan(): number;
23
27
  /** 单元格类型 */
24
28
  get subtype(): TdSubtypes;
25
29
  /**
@@ -33,4 +37,10 @@ export declare abstract class TdToken extends TableBaseToken {
33
37
  lint(start?: number, re?: RegExp): LintError[];
34
38
  /** 是否位于行首 */
35
39
  isIndependent(): boolean;
40
+ /**
41
+ * @override
42
+ * @param key 属性键
43
+ */
44
+ getAttr<T extends string>(key: T): TdAttrGetter<T>;
36
45
  }
46
+ export {};
@@ -13,6 +13,14 @@ const base_1 = require("./base");
13
13
  class TdToken extends base_1.TableBaseToken {
14
14
  type = 'td';
15
15
  #innerSyntax = '';
16
+ /** rowspan */
17
+ get rowspan() {
18
+ return this.getAttr('rowspan');
19
+ }
20
+ /** colspan */
21
+ get colspan() {
22
+ return this.getAttr('colspan');
23
+ }
16
24
  /** 单元格类型 */
17
25
  get subtype() {
18
26
  return this.#getSyntax().subtype;
@@ -113,5 +121,13 @@ class TdToken extends base_1.TableBaseToken {
113
121
  isIndependent() {
114
122
  return this.firstChild.text().startsWith('\n');
115
123
  }
124
+ /**
125
+ * @override
126
+ * @param key 属性键
127
+ */
128
+ getAttr(key) {
129
+ const value = super.getAttr(key);
130
+ return (key === 'rowspan' || key === 'colspan' ? Number(value) || 1 : value);
131
+ }
116
132
  }
117
133
  exports.TdToken = TdToken;
@@ -1,4 +1,5 @@
1
1
  import { TableBaseToken } from './base';
2
+ import { TdToken } from './td';
2
3
  import type { LintError } from '../../base';
3
4
  export interface TableCoords {
4
5
  readonly row: number;
@@ -12,4 +13,13 @@ export declare abstract class TrBaseToken extends TableBaseToken {
12
13
  type: 'table' | 'tr';
13
14
  /** @override */
14
15
  lint(start?: number, re?: RegExp): LintError[];
16
+ /** 获取行数 */
17
+ getRowCount(): number;
18
+ /**
19
+ * 获取第n列
20
+ * @param n 列号
21
+ * @param insert 是否用于判断插入新列的位置
22
+ * @throws `RangeError` 不存在对应单元格
23
+ */
24
+ getNthCol(n: number, insert?: false): TdToken | undefined;
15
25
  }
@@ -4,6 +4,7 @@ exports.TrBaseToken = void 0;
4
4
  const lint_1 = require("../../util/lint");
5
5
  const debug_1 = require("../../util/debug");
6
6
  const base_1 = require("./base");
7
+ const td_1 = require("./td");
7
8
  /** 表格行或表格 */
8
9
  class TrBaseToken extends base_1.TableBaseToken {
9
10
  /** @override */
@@ -34,5 +35,24 @@ class TrBaseToken extends base_1.TableBaseToken {
34
35
  errors.push(error);
35
36
  return errors;
36
37
  }
38
+ /** 获取行数 */
39
+ getRowCount() {
40
+ return Number(this.childNodes.some(child => child instanceof td_1.TdToken && child.isIndependent() && !child.firstChild.text().endsWith('+')));
41
+ }
42
+ getNthCol(n, insert = false) {
43
+ let last = 0;
44
+ for (const child of this.childNodes.slice(2)) {
45
+ if (child instanceof td_1.TdToken) {
46
+ if (child.isIndependent()) {
47
+ last = Number(child.subtype !== 'caption');
48
+ }
49
+ n -= last;
50
+ if (n < 0) {
51
+ return child;
52
+ }
53
+ }
54
+ }
55
+ return undefined;
56
+ }
37
57
  }
38
58
  exports.TrBaseToken = TrBaseToken;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setChildNodes = exports.isToken = exports.Shadow = void 0;
3
+ exports.emptyArray = exports.setChildNodes = exports.isToken = exports.Shadow = void 0;
4
4
  exports.Shadow = {
5
5
  running: false,
6
6
  /** @private */
@@ -31,3 +31,10 @@ const setChildNodes = (parent, position, deleteCount, inserted = []) => {
31
31
  return removed;
32
32
  };
33
33
  exports.setChildNodes = setChildNodes;
34
+ /**
35
+ * 生成一个指定长度的空数组
36
+ * @param n 数组长度
37
+ * @param callback 回调函数
38
+ */
39
+ const emptyArray = (n, callback) => new Array(n).fill(undefined).map((_, i) => callback(i));
40
+ exports.emptyArray = emptyArray;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikilint",
3
- "version": "2.5.7",
3
+ "version": "2.6.0",
4
4
  "description": "A Node.js linter for MediaWiki markup",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "scripts": {
35
35
  "declaration": "grep -rl --include='*.d.ts' '@private' dist/ | xargs bash sed.sh -i -E '/^\\s+\\/\\*\\* @private/,+1d'; node ./dist/bin/declaration.js",
36
- "prepublishOnly": "npm run build && rm dist/internal.js dist/[bmpu]*/*.d.ts",
36
+ "prepublishOnly": "npm run build && rm dist/internal.js dist/[bpu]*/*.d.ts",
37
37
  "build": "bash build.sh",
38
38
  "diff": "bash diff.sh",
39
39
  "diff:stat": "f() { git diff --stat --ignore-all-space --color=always $1 $2 -- . ':!extensions/' ':!bin/' | grep '\\.ts'; }; f",