@salesforcedevs/sfdocs-liquid-lint-filtered-blocks 1.0.1-alpha.2 → 1.0.1
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 +35 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/index.test.ts +4 -4
- package/src/index.ts +14 -31
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# liquid-lint-filtered-blocks
|
|
2
|
+
|
|
3
|
+
This remark-lint plugin validates structural issues in Liquid filter blocks
|
|
4
|
+
(`{% englishonly %}` / `{% endenglishonly %}`, `{% future %}` / `{% endfuture %}`,
|
|
5
|
+
etc.) within sfdocs Markdown files.
|
|
6
|
+
|
|
7
|
+
## What it checks
|
|
8
|
+
|
|
9
|
+
- **Redundant nesting**: a filter block inside another block of the same type is
|
|
10
|
+
redundant and likely a copy-paste mistake.
|
|
11
|
+
- **Entire file wrapped**: when all non-frontmatter content is inside a single
|
|
12
|
+
filter block, the topic should use `filters: [<tag>]` in the TOC instead.
|
|
13
|
+
- **Empty headings after removal**: headings outside a filter block whose entire
|
|
14
|
+
section body is inside that block would render as empty headings in filtered
|
|
15
|
+
builds.
|
|
16
|
+
|
|
17
|
+
## Options
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
{
|
|
21
|
+
filterTags?: string[]
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
An array of Liquid filter tag names to validate (e.g. `["englishonly", "future"]`).
|
|
26
|
+
Each tag is checked independently.
|
|
27
|
+
|
|
28
|
+
## Install & build
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
yarn install && yarn build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
>> Release versioning and commits are automated via Lerna when you merge the
|
|
35
|
+
>> release PR.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA8GA,kBAA2C"}
|
package/dist/index.js
CHANGED
|
@@ -6,24 +6,6 @@ const unified_lint_rule_1 = __importDefault(require("unified-lint-rule"));
|
|
|
6
6
|
const unist_util_visit_1 = __importDefault(require("unist-util-visit"));
|
|
7
7
|
const sfdocs_liquid_lint_utils_1 = require("@salesforcedevs/sfdocs-liquid-lint-utils");
|
|
8
8
|
const SOURCE = 'sfdocs-liquid-lint:liquid-filtered-blocks';
|
|
9
|
-
function findBlockRanges(content, tagName) {
|
|
10
|
-
const ranges = [];
|
|
11
|
-
const tagRe = new RegExp(`{%-?\\s*(${tagName}|end${tagName})\\s*-?%}`, 'g');
|
|
12
|
-
const unmatchedOpeners = [];
|
|
13
|
-
let match;
|
|
14
|
-
while ((match = tagRe.exec(content)) !== null) {
|
|
15
|
-
if (match[1] === tagName) {
|
|
16
|
-
unmatchedOpeners.push(match.index);
|
|
17
|
-
}
|
|
18
|
-
else if (unmatchedOpeners.length > 0) {
|
|
19
|
-
ranges.push({
|
|
20
|
-
openStart: unmatchedOpeners.pop(),
|
|
21
|
-
closeEnd: match.index + match[0].length,
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return ranges;
|
|
26
|
-
}
|
|
27
9
|
function isInsideRange(offset, ranges) {
|
|
28
10
|
return ranges.some(r => offset >= r.openStart && offset < r.closeEnd);
|
|
29
11
|
}
|
|
@@ -73,19 +55,24 @@ function stripFilteredBlocks(section, allRanges, sectionOffset) {
|
|
|
73
55
|
return result;
|
|
74
56
|
}
|
|
75
57
|
function validateTag(tree, file, raw, content, tagName) {
|
|
76
|
-
const ranges = findBlockRanges(content, tagName);
|
|
58
|
+
const ranges = (0, sfdocs_liquid_lint_utils_1.findBlockRanges)(content, tagName);
|
|
77
59
|
if (ranges.length === 0)
|
|
78
60
|
return;
|
|
79
61
|
for (const range of ranges) {
|
|
80
62
|
if (isNestedBlock(range, ranges)) {
|
|
81
|
-
|
|
63
|
+
if (tagName === 'englishonly') {
|
|
64
|
+
file.message(`Redundant nested englishonly block — already inside an englishonly block.`, (0, sfdocs_liquid_lint_utils_1.offsetToPosition)(raw, range.openStart));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
file.message(`Redundant nested ${tagName} block — already inside a ${tagName} block.`, (0, sfdocs_liquid_lint_utils_1.offsetToPosition)(raw, range.openStart));
|
|
68
|
+
}
|
|
82
69
|
}
|
|
83
70
|
}
|
|
84
71
|
if (isEntireFileWrapped(content, ranges)) {
|
|
85
|
-
file.message(`All content is inside ${tagName} block — use
|
|
72
|
+
file.message(`All content is inside ${tagName} block — use filters: [${tagName}] in TOC instead.`, (0, sfdocs_liquid_lint_utils_1.offsetToPosition)(raw, ranges[0].openStart));
|
|
86
73
|
}
|
|
87
74
|
for (const heading of findEmptyHeadings(tree, content, ranges)) {
|
|
88
|
-
file.message(`Heading
|
|
75
|
+
file.message(`Heading will have no content when the ${tagName} block is filtered out. Remove or adjust the heading.`, heading);
|
|
89
76
|
}
|
|
90
77
|
}
|
|
91
78
|
function checkFilteredBlocks(tree, file, option) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;AAAA,0EAAqC;AACrC,wEAAqC;AAErC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforcedevs/sfdocs-liquid-lint-filtered-blocks",
|
|
3
|
-
"version": "1.0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Validates structural issues in liquid filter blocks (englishonly, future, etc.).",
|
|
5
5
|
"author": "SFDocs Team",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@salesforcedevs/sfdocs-liquid-lint-utils": "1.0.2
|
|
17
|
+
"@salesforcedevs/sfdocs-liquid-lint-utils": "^1.0.2",
|
|
18
18
|
"unified-lint-rule": "^1.0.6",
|
|
19
19
|
"unist-util-visit": "^2.0.3",
|
|
20
20
|
"vfile": "^4.2.1"
|
|
@@ -62,7 +62,7 @@ describe('sfdocs-lint:liquid-filtered-blocks', () => {
|
|
|
62
62
|
const messages = run(md, { filterTags: ['englishonly'] }).messages;
|
|
63
63
|
expect(messages).toHaveLength(1);
|
|
64
64
|
expect(messages[0].message).toBe(
|
|
65
|
-
'Redundant nested englishonly block — already inside
|
|
65
|
+
'Redundant nested englishonly block — already inside an englishonly block.',
|
|
66
66
|
);
|
|
67
67
|
expect(messages[0].source).toBe('sfdocs-liquid-lint');
|
|
68
68
|
expect(messages[0].ruleId).toBe('liquid-filtered-blocks');
|
|
@@ -79,7 +79,7 @@ describe('sfdocs-lint:liquid-filtered-blocks', () => {
|
|
|
79
79
|
const messages = run(md, { filterTags: ['englishonly'] }).messages;
|
|
80
80
|
expect(messages).toHaveLength(1);
|
|
81
81
|
expect(messages[0].message).toBe(
|
|
82
|
-
'All content is inside englishonly block — use
|
|
82
|
+
'All content is inside englishonly block — use filters: [englishonly] in TOC instead.',
|
|
83
83
|
);
|
|
84
84
|
});
|
|
85
85
|
|
|
@@ -97,7 +97,7 @@ describe('sfdocs-lint:liquid-filtered-blocks', () => {
|
|
|
97
97
|
const messages = run(md, { filterTags: ['englishonly'] }).messages;
|
|
98
98
|
expect(messages).toHaveLength(1);
|
|
99
99
|
expect(messages[0].message).toBe(
|
|
100
|
-
'All content is inside englishonly block — use
|
|
100
|
+
'All content is inside englishonly block — use filters: [englishonly] in TOC instead.',
|
|
101
101
|
);
|
|
102
102
|
});
|
|
103
103
|
|
|
@@ -120,7 +120,7 @@ describe('sfdocs-lint:liquid-filtered-blocks', () => {
|
|
|
120
120
|
const messages = run(md, { filterTags: ['englishonly'] }).messages;
|
|
121
121
|
expect(messages).toHaveLength(1);
|
|
122
122
|
expect(messages[0].message).toBe(
|
|
123
|
-
'Heading
|
|
123
|
+
'Heading will have no content when the englishonly block is filtered out. Remove or adjust the heading.',
|
|
124
124
|
);
|
|
125
125
|
});
|
|
126
126
|
|
package/src/index.ts
CHANGED
|
@@ -1,34 +1,10 @@
|
|
|
1
1
|
import rule from 'unified-lint-rule';
|
|
2
2
|
import visit from 'unist-util-visit';
|
|
3
3
|
import { VFile } from 'vfile';
|
|
4
|
-
import { offsetToPosition, parseFrontmatter } from '@salesforcedevs/sfdocs-liquid-lint-utils';
|
|
4
|
+
import { offsetToPosition, parseFrontmatter, findBlockRanges, BlockRange } from '@salesforcedevs/sfdocs-liquid-lint-utils';
|
|
5
5
|
|
|
6
6
|
const SOURCE = 'sfdocs-liquid-lint:liquid-filtered-blocks';
|
|
7
7
|
|
|
8
|
-
interface BlockRange {
|
|
9
|
-
openStart: number;
|
|
10
|
-
closeEnd: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function findBlockRanges(content: string, tagName: string): BlockRange[] {
|
|
14
|
-
const ranges: BlockRange[] = [];
|
|
15
|
-
const tagRe = new RegExp(`{%-?\\s*(${tagName}|end${tagName})\\s*-?%}`, 'g');
|
|
16
|
-
const unmatchedOpeners: number[] = [];
|
|
17
|
-
|
|
18
|
-
let match: RegExpExecArray | null;
|
|
19
|
-
while ((match = tagRe.exec(content)) !== null) {
|
|
20
|
-
if (match[1] === tagName) {
|
|
21
|
-
unmatchedOpeners.push(match.index);
|
|
22
|
-
} else if (unmatchedOpeners.length > 0) {
|
|
23
|
-
ranges.push({
|
|
24
|
-
openStart: unmatchedOpeners.pop()!,
|
|
25
|
-
closeEnd: match.index + match[0].length,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return ranges;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
8
|
function isInsideRange(offset: number, ranges: BlockRange[]): boolean {
|
|
33
9
|
return ranges.some(r => offset >= r.openStart && offset < r.closeEnd);
|
|
34
10
|
}
|
|
@@ -88,23 +64,30 @@ function validateTag(tree: any, file: VFile, raw: string, content: string, tagNa
|
|
|
88
64
|
|
|
89
65
|
for (const range of ranges) {
|
|
90
66
|
if (isNestedBlock(range, ranges)) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
67
|
+
if (tagName === 'englishonly') {
|
|
68
|
+
file.message(
|
|
69
|
+
`Redundant nested englishonly block — already inside an englishonly block.`,
|
|
70
|
+
offsetToPosition(raw, range.openStart),
|
|
71
|
+
);
|
|
72
|
+
} else {
|
|
73
|
+
file.message(
|
|
74
|
+
`Redundant nested ${tagName} block — already inside a ${tagName} block.`,
|
|
75
|
+
offsetToPosition(raw, range.openStart),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
95
78
|
}
|
|
96
79
|
}
|
|
97
80
|
|
|
98
81
|
if (isEntireFileWrapped(content, ranges)) {
|
|
99
82
|
file.message(
|
|
100
|
-
`All content is inside ${tagName} block — use
|
|
83
|
+
`All content is inside ${tagName} block — use filters: [${tagName}] in TOC instead.`,
|
|
101
84
|
offsetToPosition(raw, ranges[0].openStart),
|
|
102
85
|
);
|
|
103
86
|
}
|
|
104
87
|
|
|
105
88
|
for (const heading of findEmptyHeadings(tree, content, ranges)) {
|
|
106
89
|
file.message(
|
|
107
|
-
`Heading
|
|
90
|
+
`Heading will have no content when the ${tagName} block is filtered out. Remove or adjust the heading.`,
|
|
108
91
|
heading,
|
|
109
92
|
);
|
|
110
93
|
}
|