ignore-lint-errors 0.5.0 → 0.7.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/README.md +1 -1
- package/dist/src/steps/create-options.js +1 -2
- package/dist/src/utils/ignore-errors/eslint.js +9 -31
- package/dist/src/utils/ignore-errors/shared/index.js +24 -0
- package/dist/src/utils/ignore-errors/stylelint.js +16 -2
- package/dist/src/utils/ignore-errors/typescript.js +58 -22
- package/dist/src/utils/linters/eslint.js +16 -33
- package/dist/src/utils/linters/shared/index.js +29 -0
- package/dist/src/utils/linters/stylelint.js +16 -33
- package/dist/src/utils/linters/typescript.js +10 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,8 +7,7 @@ function findDependencies(projectRoot) {
|
|
|
7
7
|
]);
|
|
8
8
|
return {
|
|
9
9
|
eslint: projectDependencies.has('eslint'),
|
|
10
|
-
glint: projectDependencies.has('@glint/
|
|
11
|
-
projectDependencies.has('@glint/ember-tsc'),
|
|
10
|
+
glint: projectDependencies.has('@glint/ember-tsc'),
|
|
12
11
|
stylelint: projectDependencies.has('stylelint'),
|
|
13
12
|
typescript: projectDependencies.has('typescript'),
|
|
14
13
|
};
|
|
@@ -1,44 +1,22 @@
|
|
|
1
1
|
import { EOL } from 'node:os';
|
|
2
|
-
import {
|
|
3
|
-
function getIgnoredEslintRules(lineOfCode) {
|
|
4
|
-
const traverse = AST.traverse(true);
|
|
5
|
-
let ignoredEslintRules = [];
|
|
6
|
-
try {
|
|
7
|
-
traverse(lineOfCode, {
|
|
8
|
-
visitComment(node) {
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
10
|
-
const comment = node.value.value.trim();
|
|
11
|
-
if (comment.startsWith('eslint-disable-next-line')) {
|
|
12
|
-
ignoredEslintRules = comment
|
|
13
|
-
.replace(/^eslint-disable-next-line\s+/, '')
|
|
14
|
-
.split(',')
|
|
15
|
-
.map((token) => token.trim());
|
|
16
|
-
}
|
|
17
|
-
return false;
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
// Do nothing
|
|
23
|
-
}
|
|
24
|
-
return ignoredEslintRules;
|
|
25
|
-
}
|
|
2
|
+
import { getIgnoredRules } from './shared/index.js';
|
|
26
3
|
export function ignoreErrors(file, lintErrors) {
|
|
27
4
|
const lines = file.split(EOL);
|
|
5
|
+
const ignoreDirective = 'eslint-disable-next-line';
|
|
28
6
|
lintErrors.forEach(({ line, message }) => {
|
|
29
7
|
const currentIndex = line - 1;
|
|
30
8
|
const previousIndex = Math.max(currentIndex - 1, 0);
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
9
|
+
const ignoredRules = getIgnoredRules(lines[previousIndex], {
|
|
10
|
+
ignoreDirective,
|
|
11
|
+
});
|
|
12
|
+
if (ignoredRules.length === 0) {
|
|
13
|
+
lines.splice(currentIndex, 0, `// ${ignoreDirective} ${message}`);
|
|
35
14
|
}
|
|
36
15
|
else {
|
|
37
|
-
const newMessage = [...
|
|
16
|
+
const newMessage = [...ignoredRules, ...message.split(', ')]
|
|
38
17
|
.sort()
|
|
39
18
|
.join(', ');
|
|
40
|
-
|
|
41
|
-
lines.splice(previousIndex, 1, ignoreDirective);
|
|
19
|
+
lines.splice(previousIndex, 1, `// ${ignoreDirective} ${newMessage}`);
|
|
42
20
|
}
|
|
43
21
|
});
|
|
44
22
|
return lines.join(EOL);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AST } from '@codemod-utils/ast-javascript';
|
|
2
|
+
export function getIgnoredRules(lineOfCode, options) {
|
|
3
|
+
const traverse = AST.traverse(true);
|
|
4
|
+
let ignoredRules = [];
|
|
5
|
+
try {
|
|
6
|
+
traverse(lineOfCode, {
|
|
7
|
+
visitComment(node) {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
9
|
+
const comment = node.value.value.trim();
|
|
10
|
+
if (comment.startsWith(options.ignoreDirective)) {
|
|
11
|
+
ignoredRules = comment
|
|
12
|
+
.replace(new RegExp(`^${options.ignoreDirective}\\s+`, 'g'), '')
|
|
13
|
+
.split(',')
|
|
14
|
+
.map((token) => token.trim());
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Do nothing
|
|
22
|
+
}
|
|
23
|
+
return ignoredRules;
|
|
24
|
+
}
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import { EOL } from 'node:os';
|
|
2
|
+
import { getIgnoredRules } from './shared/index.js';
|
|
2
3
|
export function ignoreErrors(file, lintErrors) {
|
|
3
4
|
const lines = file.split(EOL);
|
|
5
|
+
const ignoreDirective = 'stylelint-disable-next-line';
|
|
4
6
|
lintErrors.forEach(({ line, message }) => {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
+
const currentIndex = line - 1;
|
|
8
|
+
const previousIndex = Math.max(currentIndex - 1, 0);
|
|
9
|
+
const ignoredRules = getIgnoredRules(lines[previousIndex], {
|
|
10
|
+
ignoreDirective,
|
|
11
|
+
});
|
|
12
|
+
if (ignoredRules.length === 0) {
|
|
13
|
+
lines.splice(currentIndex, 0, `/* ${ignoreDirective} ${message} */`);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
const newMessage = [...ignoredRules, ...message.split(', ')]
|
|
17
|
+
.sort()
|
|
18
|
+
.join(', ');
|
|
19
|
+
lines.splice(previousIndex, 1, `/* ${ignoreDirective} ${newMessage} */`);
|
|
20
|
+
}
|
|
7
21
|
});
|
|
8
22
|
return lines.join(EOL);
|
|
9
23
|
}
|
|
@@ -1,56 +1,92 @@
|
|
|
1
1
|
import { EOL } from 'node:os';
|
|
2
2
|
import { AST } from '@codemod-utils/ast-template';
|
|
3
|
-
import { findTemplateTags, updateTemplates, } from '@codemod-utils/ast-template-tag';
|
|
4
|
-
|
|
3
|
+
import { findTemplateTags as upstreamFindTemplateTags, updateTemplates, } from '@codemod-utils/ast-template-tag';
|
|
4
|
+
import { getIgnoredRules } from './shared/index.js';
|
|
5
|
+
function findTemplateTags(file) {
|
|
5
6
|
function getLOC(file) {
|
|
6
7
|
const matches = file.match(/\r?\n/g);
|
|
7
8
|
return (matches ?? []).length;
|
|
8
9
|
}
|
|
9
|
-
const templateTags =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const { range } = templateTag;
|
|
10
|
+
const templateTags = upstreamFindTemplateTags(file);
|
|
11
|
+
return templateTags.map((templateTag) => {
|
|
12
|
+
const { contents, range } = templateTag;
|
|
13
13
|
const lineStart = getLOC(file.substring(0, range.startChar)) + 1;
|
|
14
14
|
const lineEnd = getLOC(file.substring(0, range.endChar)) + 1;
|
|
15
|
-
|
|
15
|
+
const lineRange = {
|
|
16
|
+
end: lineEnd,
|
|
17
|
+
start: lineStart,
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
contents,
|
|
21
|
+
lineRange,
|
|
22
|
+
};
|
|
16
23
|
});
|
|
17
|
-
return linesWithTemplate;
|
|
18
24
|
}
|
|
19
25
|
export function ignoreErrors(file, lintErrors) {
|
|
20
|
-
const linesWithTemplate = findLinesWithTemplate(file);
|
|
21
26
|
const lines = file.split(EOL);
|
|
27
|
+
const templateTags = findTemplateTags(file);
|
|
22
28
|
lintErrors.forEach(({ line, message }) => {
|
|
23
|
-
const
|
|
24
|
-
|
|
29
|
+
const currentIndex = line - 1;
|
|
30
|
+
const previousIndex = Math.max(currentIndex - 1, 0);
|
|
31
|
+
const templateTagIndex = templateTags.findIndex(({ lineRange }) => {
|
|
32
|
+
return lineRange.start <= line && line <= lineRange.end;
|
|
33
|
+
});
|
|
34
|
+
const erroredInTemplate = templateTagIndex >= 0;
|
|
35
|
+
if (erroredInTemplate) {
|
|
36
|
+
const ignoreDirective = `{{! @glint-expect-error: ${message} }}`;
|
|
37
|
+
const { contents, lineRange } = templateTags[templateTagIndex];
|
|
38
|
+
if (lineRange.start !== lineRange.end) {
|
|
39
|
+
lines.splice(currentIndex, 0, ignoreDirective);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const newTemplate = lines[currentIndex].replace(/<template>(.+)<\/template>/, `<template>${ignoreDirective}${contents}</template>`);
|
|
43
|
+
lines.splice(currentIndex, 1, newTemplate);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const ignoreDirective = `// @ts-expect-error: ${message}`;
|
|
47
|
+
const ignoredRules = getIgnoredRules(lines[previousIndex], {
|
|
48
|
+
ignoreDirective: 'eslint-disable-next-line',
|
|
25
49
|
});
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
50
|
+
if (ignoredRules.length === 0) {
|
|
51
|
+
lines.splice(currentIndex, 0, ignoreDirective);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
lines.splice(previousIndex, 0, ignoreDirective);
|
|
55
|
+
}
|
|
30
56
|
});
|
|
31
57
|
return lines.join(EOL);
|
|
32
58
|
}
|
|
33
59
|
// For fallback, ignore type checks in templates
|
|
34
60
|
export function ignoreErrorsFallback(file, lintErrors) {
|
|
35
|
-
const linesWithTemplate = findLinesWithTemplate(file);
|
|
36
61
|
const lines = file.split(EOL);
|
|
62
|
+
const templateTags = findTemplateTags(file);
|
|
37
63
|
let hasErrorInTemplate = false;
|
|
38
64
|
lintErrors.forEach(({ line, message }) => {
|
|
39
|
-
const
|
|
40
|
-
|
|
65
|
+
const currentIndex = line - 1;
|
|
66
|
+
const previousIndex = Math.max(currentIndex - 1, 0);
|
|
67
|
+
const erroredInTemplate = templateTags.some(({ lineRange }) => {
|
|
68
|
+
return lineRange.start <= line && line <= lineRange.end;
|
|
41
69
|
});
|
|
42
70
|
if (erroredInTemplate) {
|
|
43
71
|
hasErrorInTemplate = true;
|
|
44
72
|
return;
|
|
45
73
|
}
|
|
46
74
|
const ignoreDirective = `// @ts-expect-error: ${message}`;
|
|
47
|
-
|
|
75
|
+
const ignoredRules = getIgnoredRules(lines[previousIndex], {
|
|
76
|
+
ignoreDirective: 'eslint-disable-next-line',
|
|
77
|
+
});
|
|
78
|
+
if (ignoredRules.length === 0) {
|
|
79
|
+
lines.splice(currentIndex, 0, ignoreDirective);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
lines.splice(previousIndex, 0, ignoreDirective);
|
|
83
|
+
}
|
|
48
84
|
});
|
|
49
85
|
let newFile = lines.join(EOL);
|
|
50
86
|
if (hasErrorInTemplate) {
|
|
51
87
|
newFile = updateTemplates(newFile, (code) => {
|
|
52
88
|
const ignoreDirective = '{{! @glint-nocheck }}';
|
|
53
|
-
return [ignoreDirective, code].join(
|
|
89
|
+
return [ignoreDirective, code].join('');
|
|
54
90
|
});
|
|
55
91
|
}
|
|
56
92
|
return newFile;
|
|
@@ -60,8 +96,8 @@ export function isParseable(file) {
|
|
|
60
96
|
let isParseable = true;
|
|
61
97
|
try {
|
|
62
98
|
const templateTags = findTemplateTags(file);
|
|
63
|
-
templateTags.forEach((
|
|
64
|
-
traverse(
|
|
99
|
+
templateTags.forEach(({ contents }) => {
|
|
100
|
+
traverse(contents);
|
|
65
101
|
});
|
|
66
102
|
}
|
|
67
103
|
catch {
|
|
@@ -1,48 +1,31 @@
|
|
|
1
1
|
import { relative, sep } from 'node:path';
|
|
2
|
+
import { getFilesWithErrors } from './shared/index.js';
|
|
2
3
|
export const outputFilePath = '.ignore-lint-errors/eslint.txt';
|
|
3
|
-
|
|
4
|
+
function normalize(file, projectRoot) {
|
|
4
5
|
const filePathToData = new Map();
|
|
5
6
|
const records = JSON.parse(file);
|
|
6
7
|
records.forEach((record) => {
|
|
7
8
|
const { filePath: absoluteFilePath, messages } = record;
|
|
9
|
+
const filePath = relative(projectRoot, absoluteFilePath).replaceAll(sep, '/');
|
|
10
|
+
const lineToRules = new Map();
|
|
8
11
|
const data = new Map();
|
|
9
12
|
messages.forEach(({ line, ruleId }) => {
|
|
10
|
-
if (
|
|
11
|
-
|
|
13
|
+
if (lineToRules.has(line)) {
|
|
14
|
+
lineToRules.get(line).push(ruleId);
|
|
12
15
|
}
|
|
13
16
|
else {
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
const filePath = relative(projectRoot, absoluteFilePath).replaceAll(sep, '/');
|
|
18
|
-
filePathToData.set(filePath, data);
|
|
19
|
-
});
|
|
20
|
-
const filesWithErrors = [];
|
|
21
|
-
filePathToData.forEach((data, filePath) => {
|
|
22
|
-
const lintErrors = [];
|
|
23
|
-
data.forEach((allMessages, line) => {
|
|
24
|
-
const ruleIds = Array.from(new Set(allMessages.sort()));
|
|
25
|
-
lintErrors.push({
|
|
26
|
-
line,
|
|
27
|
-
message: ruleIds.join(', '),
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
if (lintErrors.length === 0) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
lintErrors.sort((a, b) => {
|
|
34
|
-
if (a.line < b.line) {
|
|
35
|
-
return 1;
|
|
36
|
-
}
|
|
37
|
-
if (b.line < a.line) {
|
|
38
|
-
return -1;
|
|
17
|
+
lineToRules.set(line, [ruleId]);
|
|
39
18
|
}
|
|
40
|
-
return 0;
|
|
41
19
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
20
|
+
lineToRules.forEach((rules, line) => {
|
|
21
|
+
const message = Array.from(new Set(rules.sort())).join(', ');
|
|
22
|
+
data.set(line, message);
|
|
45
23
|
});
|
|
24
|
+
filePathToData.set(filePath, data);
|
|
46
25
|
});
|
|
47
|
-
return
|
|
26
|
+
return filePathToData;
|
|
27
|
+
}
|
|
28
|
+
export function parseOutputFile(file, projectRoot) {
|
|
29
|
+
const filePathToData = normalize(file, projectRoot);
|
|
30
|
+
return getFilesWithErrors(filePathToData);
|
|
48
31
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function getFilesWithErrors(filePathToData) {
|
|
2
|
+
const filesWithErrors = [];
|
|
3
|
+
filePathToData.forEach((data, filePath) => {
|
|
4
|
+
const lintErrors = [];
|
|
5
|
+
data.forEach((message, line) => {
|
|
6
|
+
lintErrors.push({
|
|
7
|
+
line,
|
|
8
|
+
message,
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
if (lintErrors.length === 0) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
lintErrors.sort((a, b) => {
|
|
15
|
+
if (a.line < b.line) {
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
if (b.line < a.line) {
|
|
19
|
+
return -1;
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
22
|
+
});
|
|
23
|
+
filesWithErrors.push({
|
|
24
|
+
filePath,
|
|
25
|
+
lintErrors,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
return filesWithErrors;
|
|
29
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { relative, sep } from 'node:path';
|
|
2
|
+
import { getFilesWithErrors } from './shared/index.js';
|
|
2
3
|
export const outputFilePath = '.ignore-lint-errors/stylelint.txt';
|
|
3
|
-
|
|
4
|
+
function normalize(file, projectRoot) {
|
|
4
5
|
const filePathToData = new Map();
|
|
5
6
|
const records = JSON.parse(file);
|
|
6
7
|
records.forEach((record) => {
|
|
@@ -8,44 +9,26 @@ export function parseOutputFile(file, projectRoot) {
|
|
|
8
9
|
if (!errored) {
|
|
9
10
|
return;
|
|
10
11
|
}
|
|
12
|
+
const filePath = relative(projectRoot, absoluteFilePath).replaceAll(sep, '/');
|
|
13
|
+
const lineToRules = new Map();
|
|
11
14
|
const data = new Map();
|
|
12
15
|
warnings.forEach(({ line, rule }) => {
|
|
13
|
-
if (
|
|
14
|
-
|
|
16
|
+
if (lineToRules.has(line)) {
|
|
17
|
+
lineToRules.get(line).push(rule);
|
|
15
18
|
}
|
|
16
19
|
else {
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
const filePath = relative(projectRoot, absoluteFilePath).replaceAll(sep, '/');
|
|
21
|
-
filePathToData.set(filePath, data);
|
|
22
|
-
});
|
|
23
|
-
const filesWithErrors = [];
|
|
24
|
-
filePathToData.forEach((data, filePath) => {
|
|
25
|
-
const lintErrors = [];
|
|
26
|
-
data.forEach((allMessages, line) => {
|
|
27
|
-
const ruleIds = Array.from(new Set(allMessages.sort()));
|
|
28
|
-
lintErrors.push({
|
|
29
|
-
line,
|
|
30
|
-
message: ruleIds.join(', '),
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
if (lintErrors.length === 0) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
lintErrors.sort((a, b) => {
|
|
37
|
-
if (a.line < b.line) {
|
|
38
|
-
return 1;
|
|
39
|
-
}
|
|
40
|
-
if (b.line < a.line) {
|
|
41
|
-
return -1;
|
|
20
|
+
lineToRules.set(line, [rule]);
|
|
42
21
|
}
|
|
43
|
-
return 0;
|
|
44
22
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
23
|
+
lineToRules.forEach((rules, line) => {
|
|
24
|
+
const message = Array.from(new Set(rules.sort())).join(', ');
|
|
25
|
+
data.set(line, message);
|
|
48
26
|
});
|
|
27
|
+
filePathToData.set(filePath, data);
|
|
49
28
|
});
|
|
50
|
-
return
|
|
29
|
+
return filePathToData;
|
|
30
|
+
}
|
|
31
|
+
export function parseOutputFile(file, projectRoot) {
|
|
32
|
+
const filePathToData = normalize(file, projectRoot);
|
|
33
|
+
return getFilesWithErrors(filePathToData);
|
|
51
34
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EOL } from 'node:os';
|
|
2
|
+
import { getFilesWithErrors } from './shared/index.js';
|
|
2
3
|
export const outputFilePath = '.ignore-lint-errors/typescript.txt';
|
|
3
|
-
|
|
4
|
+
function normalize(file) {
|
|
4
5
|
const filePathToData = new Map();
|
|
5
6
|
file.split(EOL).forEach((str) => {
|
|
6
7
|
const matches = str.match(/^(.+)\((\d+),\d+\): error TS\d+: (.+)\.$/);
|
|
@@ -9,44 +10,17 @@ export function parseOutputFile(file) {
|
|
|
9
10
|
}
|
|
10
11
|
const filePath = matches[1];
|
|
11
12
|
const line = Number.parseInt(matches[2]);
|
|
12
|
-
const message =
|
|
13
|
+
const message = 'Incorrect type';
|
|
13
14
|
if (filePathToData.has(filePath)) {
|
|
14
|
-
|
|
15
|
-
filePathToData.get(filePath).get(line).push(message);
|
|
16
|
-
}
|
|
17
|
-
else {
|
|
18
|
-
filePathToData.get(filePath).set(line, [message]);
|
|
19
|
-
}
|
|
15
|
+
filePathToData.get(filePath).set(line, message);
|
|
20
16
|
}
|
|
21
17
|
else {
|
|
22
|
-
filePathToData.set(filePath, new Map([[line,
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
const filesWithErrors = [];
|
|
26
|
-
filePathToData.forEach((data, filePath) => {
|
|
27
|
-
const lintErrors = [];
|
|
28
|
-
data.forEach((_allMessages, line) => {
|
|
29
|
-
lintErrors.push({
|
|
30
|
-
line,
|
|
31
|
-
message: 'Incorrect type',
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
if (lintErrors.length === 0) {
|
|
35
|
-
return;
|
|
18
|
+
filePathToData.set(filePath, new Map([[line, message]]));
|
|
36
19
|
}
|
|
37
|
-
lintErrors.sort((a, b) => {
|
|
38
|
-
if (a.line < b.line) {
|
|
39
|
-
return 1;
|
|
40
|
-
}
|
|
41
|
-
if (b.line < a.line) {
|
|
42
|
-
return -1;
|
|
43
|
-
}
|
|
44
|
-
return 0;
|
|
45
|
-
});
|
|
46
|
-
filesWithErrors.push({
|
|
47
|
-
filePath,
|
|
48
|
-
lintErrors,
|
|
49
|
-
});
|
|
50
20
|
});
|
|
51
|
-
return
|
|
21
|
+
return filePathToData;
|
|
22
|
+
}
|
|
23
|
+
export function parseOutputFile(file) {
|
|
24
|
+
const filePathToData = normalize(file);
|
|
25
|
+
return getFilesWithErrors(filePathToData);
|
|
52
26
|
}
|