@salesforcedevs/sfdocs-liquid-lint-capture 0.0.1-alpha
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 +173 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/jest.config.js +16 -0
- package/package.json +30 -0
- package/src/__tests__/index.test.ts +451 -0
- package/src/index.ts +135 -0
- package/tsconfig.json +8 -0
- package/tsconfig.tsbuildinfo +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# liquid-lint-capture
|
|
2
|
+
|
|
3
|
+
This Markdown plugin validates the correct usage of Liquid capture blocks based on file location.
|
|
4
|
+
|
|
5
|
+
## What it checks
|
|
6
|
+
|
|
7
|
+
### For files in `shared/partials/`:
|
|
8
|
+
|
|
9
|
+
1. **Content must be in capture blocks**: All markdown content (except comments) must be inside `{% capture %}` blocks
|
|
10
|
+
2. **No duplicate capture names**: Each capture variable name must be unique within the file
|
|
11
|
+
3. **Allowed content outside capture blocks**:
|
|
12
|
+
- HTML comments: `<!-- comment -->`
|
|
13
|
+
- Liquid comments: `{% comment %}...{% endcomment %}`
|
|
14
|
+
- Whitespace
|
|
15
|
+
|
|
16
|
+
### For files outside `shared/partials/`:
|
|
17
|
+
|
|
18
|
+
1. **Capture blocks are not allowed**: Files outside the partials folder must not contain `{% capture %}` blocks
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
Partial files in `shared/partials/` are reusable snippets that define Liquid variables via capture blocks. These variables are then consumed by other markdown files through includes. Enforcing this structure ensures:
|
|
23
|
+
|
|
24
|
+
- Predictable variable definitions
|
|
25
|
+
- No unintended content rendering
|
|
26
|
+
- Prevent variable name collisions
|
|
27
|
+
- Clear separation between partials and regular content
|
|
28
|
+
|
|
29
|
+
Regular content files outside `shared/partials/` should not use capture blocks, as they are meant to render content directly, not define reusable variables. This validation prevents:
|
|
30
|
+
|
|
31
|
+
- Misplaced variable definitions in content files
|
|
32
|
+
- Confusion about file purpose and behavior
|
|
33
|
+
- Accidental capture block usage outside the partials system
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
### ✅ Valid
|
|
38
|
+
|
|
39
|
+
**Single capture block:**
|
|
40
|
+
```markdown
|
|
41
|
+
{% capture my_variable %}
|
|
42
|
+
# Heading
|
|
43
|
+
|
|
44
|
+
Content goes here.
|
|
45
|
+
{% endcapture %}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Multiple captures with comments:**
|
|
49
|
+
```markdown
|
|
50
|
+
<!-- This file defines common callouts -->
|
|
51
|
+
|
|
52
|
+
{% comment %}Success message{% endcomment %}
|
|
53
|
+
{% capture success_msg %}
|
|
54
|
+
:::tip
|
|
55
|
+
Operation completed successfully!
|
|
56
|
+
:::
|
|
57
|
+
{% endcapture %}
|
|
58
|
+
|
|
59
|
+
{% capture error_msg %}
|
|
60
|
+
:::warning
|
|
61
|
+
An error occurred.
|
|
62
|
+
:::
|
|
63
|
+
{% endcapture %}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Capture with whitespace control:**
|
|
67
|
+
```markdown
|
|
68
|
+
{%- capture trimmed_content -%}
|
|
69
|
+
No extra whitespace before/after
|
|
70
|
+
{%- endcapture -%}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### ❌ Invalid
|
|
74
|
+
|
|
75
|
+
**Content outside capture block:**
|
|
76
|
+
```markdown
|
|
77
|
+
# This heading is not captured
|
|
78
|
+
|
|
79
|
+
{% capture my_var %}
|
|
80
|
+
Captured content
|
|
81
|
+
{% endcapture %}
|
|
82
|
+
```
|
|
83
|
+
Error: `Content in shared/partials files must be inside capture blocks.`
|
|
84
|
+
|
|
85
|
+
**Duplicate capture names:**
|
|
86
|
+
```markdown
|
|
87
|
+
{% capture my_variable %}
|
|
88
|
+
First definition
|
|
89
|
+
{% endcapture %}
|
|
90
|
+
|
|
91
|
+
{% capture my_variable %}
|
|
92
|
+
Duplicate definition
|
|
93
|
+
{% endcapture %}
|
|
94
|
+
```
|
|
95
|
+
Error: `Duplicate capture block name "my_variable". This variable already exists in file.`
|
|
96
|
+
|
|
97
|
+
**Mixed issues:**
|
|
98
|
+
```markdown
|
|
99
|
+
Regular paragraph outside capture.
|
|
100
|
+
|
|
101
|
+
{% capture my_var %}First{% endcapture %}
|
|
102
|
+
{% capture my_var %}Duplicate{% endcapture %}
|
|
103
|
+
```
|
|
104
|
+
Errors:
|
|
105
|
+
- `Content in shared/partials files must be inside capture blocks.`
|
|
106
|
+
- `Duplicate capture block name "my_var"`
|
|
107
|
+
|
|
108
|
+
### ❌ Invalid (Files outside shared/partials)
|
|
109
|
+
|
|
110
|
+
**Capture block in regular content file:**
|
|
111
|
+
```markdown
|
|
112
|
+
# Guide Title
|
|
113
|
+
|
|
114
|
+
{% capture my_variable %}
|
|
115
|
+
Some content
|
|
116
|
+
{% endcapture %}
|
|
117
|
+
|
|
118
|
+
Regular content here.
|
|
119
|
+
```
|
|
120
|
+
Error: `Capture blocks are not allowed in files outside shared/partials folder.`
|
|
121
|
+
|
|
122
|
+
## File Scope
|
|
123
|
+
|
|
124
|
+
This rule applies to **all** markdown files with different validation rules based on location:
|
|
125
|
+
|
|
126
|
+
### Files in `shared/partials/`:
|
|
127
|
+
- **Must** use capture blocks for all content
|
|
128
|
+
- **Must** have unique capture block names
|
|
129
|
+
- `/content/shared/partials/common.md` ✅
|
|
130
|
+
- `/docs/shared/partials/alerts.md` ✅
|
|
131
|
+
- `shared/partials/buttons.md` ✅
|
|
132
|
+
|
|
133
|
+
### Files outside `shared/partials/`:
|
|
134
|
+
- **Must not** contain capture blocks
|
|
135
|
+
- `/content/guides/example.md` ✅
|
|
136
|
+
- `/shared/snippets/code.md` ✅
|
|
137
|
+
- `docs/tutorials/intro.md` ✅
|
|
138
|
+
|
|
139
|
+
## Install & build
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
yarn install && yarn build
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Testing
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
yarn test
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Usage
|
|
152
|
+
|
|
153
|
+
In your unified pipeline:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import unified from 'unified';
|
|
157
|
+
import parse from 'remark-parse';
|
|
158
|
+
import liquidLintPartialsCapture from '@salesforcedevs/sfdocs-liquid-lint-capture';
|
|
159
|
+
|
|
160
|
+
const processor = unified()
|
|
161
|
+
.use(parse)
|
|
162
|
+
.use(liquidLintPartialsCapture);
|
|
163
|
+
|
|
164
|
+
const file = await processor.process(markdownContent);
|
|
165
|
+
|
|
166
|
+
if (file.messages.length > 0) {
|
|
167
|
+
file.messages.forEach(msg => {
|
|
168
|
+
console.log(`${msg.line}:${msg.column}: ${msg.message}`);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* For .md files in shared/partials folder,
|
|
3
|
+
* all content must be in capture blocks
|
|
4
|
+
* only HTML and Liquid comments are allowed outside capture blocks
|
|
5
|
+
* we should not have duplicate names for capture blocks
|
|
6
|
+
*
|
|
7
|
+
* For .md files outside shared/partials folder, a file must not contain capture block
|
|
8
|
+
*/
|
|
9
|
+
declare const _default: any;
|
|
10
|
+
export = _default;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;;AA+HH,kBAA0C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* For .md files in shared/partials folder,
|
|
4
|
+
* all content must be in capture blocks
|
|
5
|
+
* only HTML and Liquid comments are allowed outside capture blocks
|
|
6
|
+
* we should not have duplicate names for capture blocks
|
|
7
|
+
*
|
|
8
|
+
* For .md files outside shared/partials folder, a file must not contain capture block
|
|
9
|
+
*/
|
|
10
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
|
+
};
|
|
13
|
+
const unified_lint_rule_1 = __importDefault(require("unified-lint-rule"));
|
|
14
|
+
const sfdocs_liquid_lint_utils_1 = require("@salesforcedevs/sfdocs-liquid-lint-utils");
|
|
15
|
+
const SOURCE = 'sfdocs-liquid-lint:liquid-capture';
|
|
16
|
+
// Regex patterns
|
|
17
|
+
const CAPTURE_BLOCK_RE = /{%-?\s*capture\s+(\w+)\s*-?%}([\s\S]*?){%-?\s*endcapture\s*-?%}/g;
|
|
18
|
+
const HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;
|
|
19
|
+
const LIQUID_COMMENT_RE = /{%-?\s*comment\s*-?%}[\s\S]*?{%-?\s*endcomment\s*-?%}/g;
|
|
20
|
+
function isInPartialsFolder(filePath) {
|
|
21
|
+
return filePath.includes('/shared/partials/');
|
|
22
|
+
}
|
|
23
|
+
function extractCaptureBlocks(content) {
|
|
24
|
+
const captures = [];
|
|
25
|
+
const re = new RegExp(CAPTURE_BLOCK_RE.source, 'g');
|
|
26
|
+
let match;
|
|
27
|
+
while ((match = re.exec(content)) !== null) {
|
|
28
|
+
captures.push({
|
|
29
|
+
name: match[1],
|
|
30
|
+
startOffset: match.index,
|
|
31
|
+
endOffset: match.index + match[0].length,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return captures;
|
|
35
|
+
}
|
|
36
|
+
function findDuplicateCaptures(captures) {
|
|
37
|
+
const nameMap = new Map();
|
|
38
|
+
for (const capture of captures) {
|
|
39
|
+
if (!nameMap.has(capture.name)) {
|
|
40
|
+
nameMap.set(capture.name, []);
|
|
41
|
+
}
|
|
42
|
+
nameMap.get(capture.name).push(capture);
|
|
43
|
+
}
|
|
44
|
+
// Filter to only duplicates
|
|
45
|
+
const duplicates = new Map();
|
|
46
|
+
for (const [name, blocks] of nameMap.entries()) {
|
|
47
|
+
if (blocks.length > 1) {
|
|
48
|
+
duplicates.set(name, blocks);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return duplicates;
|
|
52
|
+
}
|
|
53
|
+
function removeAllowedContent(content) {
|
|
54
|
+
let cleaned = content;
|
|
55
|
+
cleaned = cleaned.replace(CAPTURE_BLOCK_RE, '');
|
|
56
|
+
cleaned = cleaned.replace(HTML_COMMENT_RE, '');
|
|
57
|
+
cleaned = cleaned.replace(LIQUID_COMMENT_RE, '');
|
|
58
|
+
return cleaned;
|
|
59
|
+
}
|
|
60
|
+
function hasDisallowedContent(cleaned) {
|
|
61
|
+
return /\S/.test(cleaned);
|
|
62
|
+
}
|
|
63
|
+
function checkLiquidCapture(_tree, file) {
|
|
64
|
+
var _a;
|
|
65
|
+
if (!file.path)
|
|
66
|
+
return;
|
|
67
|
+
const raw = String((_a = file.contents) !== null && _a !== void 0 ? _a : '');
|
|
68
|
+
if (!raw)
|
|
69
|
+
return;
|
|
70
|
+
const captureBlocks = extractCaptureBlocks(raw);
|
|
71
|
+
if (!isInPartialsFolder(file.path)) {
|
|
72
|
+
if (captureBlocks.length > 0) {
|
|
73
|
+
for (const capture of captureBlocks) {
|
|
74
|
+
const position = (0, sfdocs_liquid_lint_utils_1.offsetToPosition)(raw, capture.startOffset);
|
|
75
|
+
file.message(`Capture blocks are not allowed in files outside shared/partials folder. Found capture block "${capture.name}".`, position);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const duplicates = findDuplicateCaptures(captureBlocks);
|
|
81
|
+
if (duplicates.size > 0) {
|
|
82
|
+
for (const [name, blocks] of duplicates.entries()) {
|
|
83
|
+
// Report on all occurrences after the first
|
|
84
|
+
for (let i = 1; i < blocks.length; i++) {
|
|
85
|
+
const position = (0, sfdocs_liquid_lint_utils_1.offsetToPosition)(raw, blocks[i].startOffset);
|
|
86
|
+
file.message(`Duplicate capture block name "${name}". This variable already exists in file.`, position);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Check for disallowed content outside capture blocks and comments
|
|
91
|
+
const cleaned = removeAllowedContent(raw);
|
|
92
|
+
if (hasDisallowedContent(cleaned)) {
|
|
93
|
+
// Find the first line with disallowed content for better error reporting
|
|
94
|
+
const lines = cleaned.split('\n');
|
|
95
|
+
for (const line of lines) {
|
|
96
|
+
if (/\S/.test(line)) {
|
|
97
|
+
file.message(`Content in shared/partials files must be inside capture blocks. Found content outside capture blocks: "${line.trim().substring(0, 50)}..."`);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
module.exports = (0, unified_lint_rule_1.default)(SOURCE, checkLiquidCapture);
|
|
104
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const BASE_CONFIG = require('../../scripts/jest/common.config');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
...BASE_CONFIG,
|
|
5
|
+
displayName: 'liquid-lint-capture',
|
|
6
|
+
collectCoverageFrom: ['<rootDir>/**/src/*.{ts,js}'],
|
|
7
|
+
coveragePathIgnorePatterns: ['/node_modules/', '/dist/'],
|
|
8
|
+
testMatch: ['<rootDir>/**/__tests__/**/*.{test,spec}.ts'],
|
|
9
|
+
testPathIgnorePatterns: ['lib/', 'node_modules/'],
|
|
10
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
11
|
+
preset: 'ts-jest',
|
|
12
|
+
testEnvironment: 'node',
|
|
13
|
+
transform: {
|
|
14
|
+
'^.+\\.ts$': 'ts-jest',
|
|
15
|
+
},
|
|
16
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@salesforcedevs/sfdocs-liquid-lint-capture",
|
|
3
|
+
"version": "0.0.1-alpha",
|
|
4
|
+
"description": "Validates '{% capture tag_name %} \"...\" {% endcapture %}' Liquid capture under shared/partials markdown files",
|
|
5
|
+
"author": "SFDocs Team",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "yarn compile",
|
|
9
|
+
"compile": "tsc -p tsconfig.json",
|
|
10
|
+
"prepublish": "yarn build",
|
|
11
|
+
"test": "jest",
|
|
12
|
+
"test:ci": "yarn test --maxWorkers=1 --ci --coverage",
|
|
13
|
+
"watch": "tsc --watch"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@salesforcedevs/sfdocs-liquid-lint-utils": "^1.0.0",
|
|
18
|
+
"unified-lint-rule": "^1.0.6",
|
|
19
|
+
"vfile": "^4.2.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/jest": "^25.2.2",
|
|
23
|
+
"@types/node": "^15.6.1",
|
|
24
|
+
"dedent": "^0.7.0",
|
|
25
|
+
"jest": "^29.7.0",
|
|
26
|
+
"ts-jest": "^29.2.5",
|
|
27
|
+
"typescript": "^4.4.2",
|
|
28
|
+
"unified": "^9.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import dedent from 'dedent';
|
|
2
|
+
import unified from 'unified';
|
|
3
|
+
import markdown from 'remark-parse';
|
|
4
|
+
import stringify from 'remark-stringify';
|
|
5
|
+
import vfile, { VFile } from 'vfile';
|
|
6
|
+
import plugin from '../index';
|
|
7
|
+
|
|
8
|
+
const processMarkdown = (md: string, filePath: string): VFile => {
|
|
9
|
+
const file = vfile({ path: filePath, contents: md });
|
|
10
|
+
return unified()
|
|
11
|
+
.use(markdown)
|
|
12
|
+
.use(plugin as unified.Attacher)
|
|
13
|
+
.use(stringify)
|
|
14
|
+
.processSync(file);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('sfdocs-liquid-lint:partials-capture', () => {
|
|
18
|
+
describe('file path filtering', () => {
|
|
19
|
+
test('ignores regular content in files not in shared/partials folder', () => {
|
|
20
|
+
const md = dedent(`
|
|
21
|
+
# This is a regular heading
|
|
22
|
+
|
|
23
|
+
Some content here.
|
|
24
|
+
`);
|
|
25
|
+
const result = processMarkdown(md, '/content/guides/example.md');
|
|
26
|
+
expect(result.messages).toHaveLength(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('disallows capture blocks in files outside shared/partials folder', () => {
|
|
30
|
+
const md = dedent(`
|
|
31
|
+
# This is a regular heading
|
|
32
|
+
|
|
33
|
+
{% capture my_var %}
|
|
34
|
+
Some content
|
|
35
|
+
{% endcapture %}
|
|
36
|
+
`);
|
|
37
|
+
const result = processMarkdown(md, '/content/guides/example.md');
|
|
38
|
+
expect(result.messages).toHaveLength(1);
|
|
39
|
+
expect(result.messages[0].message).toContain('Capture blocks are not allowed in files outside shared/partials folder');
|
|
40
|
+
expect(result.messages[0].message).toContain('my_var');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('reports all capture blocks in non-partials files', () => {
|
|
44
|
+
const md = dedent(`
|
|
45
|
+
{% capture var1 %}Content 1{% endcapture %}
|
|
46
|
+
{% capture var2 %}Content 2{% endcapture %}
|
|
47
|
+
{% capture var3 %}Content 3{% endcapture %}
|
|
48
|
+
`);
|
|
49
|
+
const result = processMarkdown(md, '/docs/guides/example.md');
|
|
50
|
+
expect(result.messages).toHaveLength(3);
|
|
51
|
+
expect(result.messages[0].message).toContain('var1');
|
|
52
|
+
expect(result.messages[1].message).toContain('var2');
|
|
53
|
+
expect(result.messages[2].message).toContain('var3');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('allows normal content but disallows only captures in non-partials', () => {
|
|
57
|
+
const md = dedent(`
|
|
58
|
+
# Normal Heading
|
|
59
|
+
|
|
60
|
+
Regular paragraph content with **formatting**.
|
|
61
|
+
|
|
62
|
+
- List item 1
|
|
63
|
+
- List item 2
|
|
64
|
+
|
|
65
|
+
{% capture should_fail %}Captured content{% endcapture %}
|
|
66
|
+
|
|
67
|
+
More normal content here.
|
|
68
|
+
|
|
69
|
+
{% capture also_fails %}Another capture{% endcapture %}
|
|
70
|
+
`);
|
|
71
|
+
const result = processMarkdown(md, '/docs/guide.md');
|
|
72
|
+
expect(result.messages).toHaveLength(2);
|
|
73
|
+
expect(result.messages[0].message).toContain('not allowed in files outside shared/partials');
|
|
74
|
+
expect(result.messages[0].message).toContain('should_fail');
|
|
75
|
+
expect(result.messages[1].message).toContain('also_fails');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('reports correct line positions for captures in non-partials', () => {
|
|
79
|
+
const md = dedent(`
|
|
80
|
+
# Title
|
|
81
|
+
|
|
82
|
+
{% capture var1 %}First{% endcapture %}
|
|
83
|
+
|
|
84
|
+
Some content
|
|
85
|
+
|
|
86
|
+
{% capture var2 %}Second{% endcapture %}
|
|
87
|
+
`);
|
|
88
|
+
const result = processMarkdown(md, '/docs/page.md');
|
|
89
|
+
expect(result.messages).toHaveLength(2);
|
|
90
|
+
expect(result.messages[0].line).toBe(3);
|
|
91
|
+
expect(result.messages[1].line).toBe(7);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('validates files in shared/partials folder', () => {
|
|
95
|
+
const md = dedent(`
|
|
96
|
+
# This is a heading
|
|
97
|
+
`);
|
|
98
|
+
const result = processMarkdown(md, '/content/shared/partials/example.md');
|
|
99
|
+
expect(result.messages.length).toBeGreaterThan(0);
|
|
100
|
+
expect(result.messages[0].message).toContain('must be inside capture blocks');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('valid capture blocks in partials', () => {
|
|
105
|
+
test('allows single capture block', () => {
|
|
106
|
+
const md = dedent(`
|
|
107
|
+
{% capture my_variable %}
|
|
108
|
+
This is captured content.
|
|
109
|
+
{% endcapture %}
|
|
110
|
+
`);
|
|
111
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
112
|
+
expect(result.messages).toHaveLength(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('allows multiple capture blocks with different names', () => {
|
|
116
|
+
const md = dedent(`
|
|
117
|
+
{% capture var1 %}
|
|
118
|
+
Content 1
|
|
119
|
+
{% endcapture %}
|
|
120
|
+
|
|
121
|
+
{% capture var2 %}
|
|
122
|
+
Content 2
|
|
123
|
+
{% endcapture %}
|
|
124
|
+
|
|
125
|
+
{% capture var3 %}
|
|
126
|
+
Content 3
|
|
127
|
+
{% endcapture %}
|
|
128
|
+
`);
|
|
129
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
130
|
+
expect(result.messages).toHaveLength(0);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('allows capture blocks with whitespace control', () => {
|
|
134
|
+
const md = dedent(`
|
|
135
|
+
{%- capture my_variable -%}
|
|
136
|
+
Trimmed content
|
|
137
|
+
{%- endcapture -%}
|
|
138
|
+
`);
|
|
139
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
140
|
+
expect(result.messages).toHaveLength(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('disallows whitespace-controlled captures in non-partials', () => {
|
|
144
|
+
const md = '{%- capture my_var -%}Content{%- endcapture -%}';
|
|
145
|
+
const result = processMarkdown(md, '/content/docs/page.md');
|
|
146
|
+
expect(result.messages).toHaveLength(1);
|
|
147
|
+
expect(result.messages[0].message).toContain('not allowed in files outside shared/partials');
|
|
148
|
+
expect(result.messages[0].message).toContain('my_var');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('allows capture blocks with markdown content inside', () => {
|
|
152
|
+
const md = dedent(`
|
|
153
|
+
{% capture rich_content %}
|
|
154
|
+
# Heading inside capture
|
|
155
|
+
|
|
156
|
+
This is a paragraph with **bold** and *italic*.
|
|
157
|
+
|
|
158
|
+
- List item 1
|
|
159
|
+
- List item 2
|
|
160
|
+
{% endcapture %}
|
|
161
|
+
`);
|
|
162
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
163
|
+
expect(result.messages).toHaveLength(0);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('comments', () => {
|
|
168
|
+
test('allows HTML comments', () => {
|
|
169
|
+
const md = dedent(`
|
|
170
|
+
<!-- This is an HTML comment -->
|
|
171
|
+
{% capture my_var %}Content{% endcapture %}
|
|
172
|
+
`);
|
|
173
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
174
|
+
expect(result.messages).toHaveLength(0);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('allows multi-line HTML comments', () => {
|
|
178
|
+
const md = dedent(`
|
|
179
|
+
<!--
|
|
180
|
+
This is a multi-line
|
|
181
|
+
HTML comment
|
|
182
|
+
-->
|
|
183
|
+
{% capture my_var %}Content{% endcapture %}
|
|
184
|
+
`);
|
|
185
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
186
|
+
expect(result.messages).toHaveLength(0);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('allows Liquid comment blocks', () => {
|
|
190
|
+
const md = dedent(`
|
|
191
|
+
{% comment %}
|
|
192
|
+
This is a Liquid comment
|
|
193
|
+
{% endcomment %}
|
|
194
|
+
{% capture my_var %}Content{% endcapture %}
|
|
195
|
+
`);
|
|
196
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
197
|
+
expect(result.messages).toHaveLength(0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('allows Liquid comments with whitespace control', () => {
|
|
201
|
+
const md = dedent(`
|
|
202
|
+
{%- comment -%}
|
|
203
|
+
Trimmed comment
|
|
204
|
+
{%- endcomment -%}
|
|
205
|
+
{% capture my_var %}Content{% endcapture %}
|
|
206
|
+
`);
|
|
207
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
208
|
+
expect(result.messages).toHaveLength(0);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('allows mix of HTML and Liquid comments', () => {
|
|
212
|
+
const md = dedent(`
|
|
213
|
+
<!-- HTML comment -->
|
|
214
|
+
{% comment %}Liquid comment{% endcomment %}
|
|
215
|
+
{% capture my_var %}Content{% endcapture %}
|
|
216
|
+
<!-- Another HTML comment -->
|
|
217
|
+
`);
|
|
218
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
219
|
+
expect(result.messages).toHaveLength(0);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('duplicate capture names', () => {
|
|
224
|
+
test('reports duplicate capture block names', () => {
|
|
225
|
+
const md = dedent(`
|
|
226
|
+
{% capture my_variable %}
|
|
227
|
+
First capture
|
|
228
|
+
{% endcapture %}
|
|
229
|
+
|
|
230
|
+
{% capture my_variable %}
|
|
231
|
+
Duplicate capture
|
|
232
|
+
{% endcapture %}
|
|
233
|
+
`);
|
|
234
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
235
|
+
expect(result.messages).toHaveLength(1);
|
|
236
|
+
expect(result.messages[0].message).toContain('Duplicate capture block name "my_variable"');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('reports multiple duplicates', () => {
|
|
240
|
+
const md = dedent(`
|
|
241
|
+
{% capture var1 %}First{% endcapture %}
|
|
242
|
+
{% capture var2 %}First{% endcapture %}
|
|
243
|
+
{% capture var1 %}Duplicate 1{% endcapture %}
|
|
244
|
+
{% capture var2 %}Duplicate 2{% endcapture %}
|
|
245
|
+
`);
|
|
246
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
247
|
+
expect(result.messages).toHaveLength(2);
|
|
248
|
+
expect(result.messages[0].message).toContain('Duplicate capture block name "var1"');
|
|
249
|
+
expect(result.messages[1].message).toContain('Duplicate capture block name "var2"');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('reports all occurrences after the first', () => {
|
|
253
|
+
const md = dedent(`
|
|
254
|
+
{% capture my_var %}First{% endcapture %}
|
|
255
|
+
{% capture my_var %}Second{% endcapture %}
|
|
256
|
+
{% capture my_var %}Third{% endcapture %}
|
|
257
|
+
`);
|
|
258
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
259
|
+
expect(result.messages).toHaveLength(2);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('disallowed content', () => {
|
|
264
|
+
test('reports markdown heading outside capture', () => {
|
|
265
|
+
const md = dedent(`
|
|
266
|
+
# This is a heading
|
|
267
|
+
|
|
268
|
+
{% capture my_var %}Content{% endcapture %}
|
|
269
|
+
`);
|
|
270
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
271
|
+
expect(result.messages.length).toBeGreaterThan(0);
|
|
272
|
+
expect(result.messages[0].message).toContain('must be inside capture blocks');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('reports paragraph content outside capture', () => {
|
|
276
|
+
const md = dedent(`
|
|
277
|
+
{% capture my_var %}Captured{% endcapture %}
|
|
278
|
+
|
|
279
|
+
This is regular paragraph content.
|
|
280
|
+
`);
|
|
281
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
282
|
+
expect(result.messages.length).toBeGreaterThan(0);
|
|
283
|
+
expect(result.messages[0].message).toContain('must be inside capture blocks');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('reports list outside capture', () => {
|
|
287
|
+
const md = dedent(`
|
|
288
|
+
- List item 1
|
|
289
|
+
- List item 2
|
|
290
|
+
|
|
291
|
+
{% capture my_var %}Captured{% endcapture %}
|
|
292
|
+
`);
|
|
293
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
294
|
+
expect(result.messages.length).toBeGreaterThan(0);
|
|
295
|
+
expect(result.messages[0].message).toContain('must be inside capture blocks');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('reports code block outside capture', () => {
|
|
299
|
+
const md = dedent(`
|
|
300
|
+
\`\`\`js
|
|
301
|
+
const x = 1;
|
|
302
|
+
\`\`\`
|
|
303
|
+
|
|
304
|
+
{% capture my_var %}Captured{% endcapture %}
|
|
305
|
+
`);
|
|
306
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
307
|
+
expect(result.messages.length).toBeGreaterThan(0);
|
|
308
|
+
expect(result.messages[0].message).toContain('must be inside capture blocks');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('allows whitespace outside capture blocks', () => {
|
|
312
|
+
const md = dedent(`
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
{% capture my_var %}Content{% endcapture %}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
{% capture another_var %}More content{% endcapture %}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
`);
|
|
322
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
323
|
+
expect(result.messages).toHaveLength(0);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe('complex scenarios', () => {
|
|
328
|
+
test('valid file with multiple captures and comments', () => {
|
|
329
|
+
const md = dedent(`
|
|
330
|
+
<!-- Header comment explaining this file -->
|
|
331
|
+
|
|
332
|
+
{% comment %}
|
|
333
|
+
This section defines variable 1
|
|
334
|
+
{% endcomment %}
|
|
335
|
+
{% capture variable_one %}
|
|
336
|
+
# Section 1
|
|
337
|
+
|
|
338
|
+
Content for variable one.
|
|
339
|
+
{% endcapture %}
|
|
340
|
+
|
|
341
|
+
<!-- Separator comment -->
|
|
342
|
+
|
|
343
|
+
{% capture variable_two %}
|
|
344
|
+
## Section 2
|
|
345
|
+
|
|
346
|
+
Content for variable two with **formatting**.
|
|
347
|
+
{% endcapture %}
|
|
348
|
+
|
|
349
|
+
{%- comment -%}End of file{%- endcomment -%}
|
|
350
|
+
`);
|
|
351
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
352
|
+
expect(result.messages).toHaveLength(0);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('reports both duplicate names and disallowed content', () => {
|
|
356
|
+
const md = dedent(`
|
|
357
|
+
# Invalid heading
|
|
358
|
+
|
|
359
|
+
{% capture my_var %}First{% endcapture %}
|
|
360
|
+
{% capture my_var %}Duplicate{% endcapture %}
|
|
361
|
+
`);
|
|
362
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
363
|
+
expect(result.messages.length).toBeGreaterThan(0);
|
|
364
|
+
// Should have both error types
|
|
365
|
+
const messages = result.messages.map(m => m.message);
|
|
366
|
+
expect(messages.some(m => m.includes('Duplicate capture'))).toBe(true);
|
|
367
|
+
expect(messages.some(m => m.includes('must be inside capture blocks'))).toBe(true);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('handles nested liquid tags inside capture', () => {
|
|
371
|
+
const md = dedent(`
|
|
372
|
+
{% capture my_content %}
|
|
373
|
+
This has {% if true %}conditional{% endif %} content.
|
|
374
|
+
And a {{ variable }} reference.
|
|
375
|
+
{% endcapture %}
|
|
376
|
+
`);
|
|
377
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
378
|
+
expect(result.messages).toHaveLength(0);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe('edge cases', () => {
|
|
383
|
+
test('handles empty file', () => {
|
|
384
|
+
const md = '';
|
|
385
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
386
|
+
expect(result.messages).toHaveLength(0);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test('handles file with only comments', () => {
|
|
390
|
+
const md = dedent(`
|
|
391
|
+
<!-- Only comments here -->
|
|
392
|
+
{% comment %}No capture blocks{% endcomment %}
|
|
393
|
+
`);
|
|
394
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
395
|
+
expect(result.messages).toHaveLength(0);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test('handles file with only whitespace', () => {
|
|
399
|
+
const md = ' \n\n\t\n ';
|
|
400
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
401
|
+
expect(result.messages).toHaveLength(0);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('handles capture block with underscores and numbers in name', () => {
|
|
405
|
+
const md = dedent(`
|
|
406
|
+
{% capture my_var_123 %}Content{% endcapture %}
|
|
407
|
+
`);
|
|
408
|
+
const result = processMarkdown(md, '/shared/partials/example.md');
|
|
409
|
+
expect(result.messages).toHaveLength(0);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test('handles missing file path gracefully', () => {
|
|
413
|
+
const file = vfile({ contents: '{% capture x %}test{% endcapture %}' });
|
|
414
|
+
const result = unified()
|
|
415
|
+
.use(markdown)
|
|
416
|
+
.use(plugin as unified.Attacher)
|
|
417
|
+
.use(stringify)
|
|
418
|
+
.processSync(file);
|
|
419
|
+
expect(result.messages).toHaveLength(0);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test('validates various path formats for partials', () => {
|
|
423
|
+
const md = '{% capture x %}Content{% endcapture %}';
|
|
424
|
+
|
|
425
|
+
// These should all be treated as partials (valid) - must contain '/shared/partials/'
|
|
426
|
+
expect(processMarkdown(md, '/app/shared/partials/x.md').messages).toHaveLength(0);
|
|
427
|
+
expect(processMarkdown(md, '/shared/partials/x.md').messages).toHaveLength(0);
|
|
428
|
+
expect(processMarkdown(md, '/content/shared/partials/alerts.md').messages).toHaveLength(0);
|
|
429
|
+
expect(processMarkdown(md, 'docs/shared/partials/alerts.md').messages).toHaveLength(0);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test('validates various path formats for non-partials', () => {
|
|
433
|
+
const md = '{% capture x %}Content{% endcapture %}';
|
|
434
|
+
|
|
435
|
+
// These should all be treated as non-partials (invalid)
|
|
436
|
+
expect(processMarkdown(md, '/docs/guides/intro.md').messages).toHaveLength(1);
|
|
437
|
+
expect(processMarkdown(md, 'content/tutorials/example.md').messages).toHaveLength(1);
|
|
438
|
+
expect(processMarkdown(md, '/shared/snippets/code.md').messages).toHaveLength(1);
|
|
439
|
+
expect(processMarkdown(md, 'README.md').messages).toHaveLength(1);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test('does not match similar but different paths', () => {
|
|
443
|
+
const md = '# Heading';
|
|
444
|
+
|
|
445
|
+
// These paths should NOT be treated as partials
|
|
446
|
+
expect(processMarkdown(md, '/shared-partials/x.md').messages).toHaveLength(0);
|
|
447
|
+
expect(processMarkdown(md, '/sharedpartials/x.md').messages).toHaveLength(0);
|
|
448
|
+
expect(processMarkdown(md, '/my-shared/partials/x.md').messages).toHaveLength(0);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* For .md files in shared/partials folder,
|
|
3
|
+
* all content must be in capture blocks
|
|
4
|
+
* only HTML and Liquid comments are allowed outside capture blocks
|
|
5
|
+
* we should not have duplicate names for capture blocks
|
|
6
|
+
*
|
|
7
|
+
* For .md files outside shared/partials folder, a file must not contain capture block
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import rule from 'unified-lint-rule';
|
|
11
|
+
import { VFile } from 'vfile';
|
|
12
|
+
import { offsetToPosition } from '@salesforcedevs/sfdocs-liquid-lint-utils';
|
|
13
|
+
|
|
14
|
+
const SOURCE = 'sfdocs-liquid-lint:liquid-capture';
|
|
15
|
+
|
|
16
|
+
// Regex patterns
|
|
17
|
+
const CAPTURE_BLOCK_RE = /{%-?\s*capture\s+(\w+)\s*-?%}([\s\S]*?){%-?\s*endcapture\s*-?%}/g;
|
|
18
|
+
const HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;
|
|
19
|
+
const LIQUID_COMMENT_RE = /{%-?\s*comment\s*-?%}[\s\S]*?{%-?\s*endcomment\s*-?%}/g;
|
|
20
|
+
|
|
21
|
+
interface CaptureBlock {
|
|
22
|
+
name: string;
|
|
23
|
+
startOffset: number;
|
|
24
|
+
endOffset: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isInPartialsFolder(filePath: string): boolean {
|
|
28
|
+
return filePath.includes('/shared/partials/');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function extractCaptureBlocks(content: string): CaptureBlock[] {
|
|
32
|
+
const captures: CaptureBlock[] = [];
|
|
33
|
+
const re = new RegExp(CAPTURE_BLOCK_RE.source, 'g');
|
|
34
|
+
let match: RegExpExecArray | null;
|
|
35
|
+
|
|
36
|
+
while ((match = re.exec(content)) !== null) {
|
|
37
|
+
captures.push({
|
|
38
|
+
name: match[1],
|
|
39
|
+
startOffset: match.index,
|
|
40
|
+
endOffset: match.index + match[0].length,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return captures;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function findDuplicateCaptures(captures: CaptureBlock[]): Map<string, CaptureBlock[]> {
|
|
48
|
+
const nameMap = new Map<string, CaptureBlock[]>();
|
|
49
|
+
|
|
50
|
+
for (const capture of captures) {
|
|
51
|
+
if (!nameMap.has(capture.name)) {
|
|
52
|
+
nameMap.set(capture.name, []);
|
|
53
|
+
}
|
|
54
|
+
nameMap.get(capture.name)!.push(capture);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Filter to only duplicates
|
|
58
|
+
const duplicates = new Map<string, CaptureBlock[]>();
|
|
59
|
+
for (const [name, blocks] of nameMap.entries()) {
|
|
60
|
+
if (blocks.length > 1) {
|
|
61
|
+
duplicates.set(name, blocks);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return duplicates;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function removeAllowedContent(content: string): string {
|
|
69
|
+
let cleaned = content;
|
|
70
|
+
|
|
71
|
+
cleaned = cleaned.replace(CAPTURE_BLOCK_RE, '');
|
|
72
|
+
|
|
73
|
+
cleaned = cleaned.replace(HTML_COMMENT_RE, '');
|
|
74
|
+
|
|
75
|
+
cleaned = cleaned.replace(LIQUID_COMMENT_RE, '');
|
|
76
|
+
|
|
77
|
+
return cleaned;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasDisallowedContent(cleaned: string): boolean {
|
|
81
|
+
return /\S/.test(cleaned);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function checkLiquidCapture(_tree: unknown, file: VFile): void {
|
|
85
|
+
if (!file.path) return;
|
|
86
|
+
|
|
87
|
+
const raw = String(file.contents ?? '');
|
|
88
|
+
if (!raw) return;
|
|
89
|
+
|
|
90
|
+
const captureBlocks = extractCaptureBlocks(raw);
|
|
91
|
+
|
|
92
|
+
if (!isInPartialsFolder(file.path)) {
|
|
93
|
+
if (captureBlocks.length > 0) {
|
|
94
|
+
for (const capture of captureBlocks) {
|
|
95
|
+
const position = offsetToPosition(raw, capture.startOffset);
|
|
96
|
+
file.message(
|
|
97
|
+
`Capture blocks are not allowed in files outside shared/partials folder. Found capture block "${capture.name}".`,
|
|
98
|
+
position
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const duplicates = findDuplicateCaptures(captureBlocks);
|
|
106
|
+
if (duplicates.size > 0) {
|
|
107
|
+
for (const [name, blocks] of duplicates.entries()) {
|
|
108
|
+
// Report on all occurrences after the first
|
|
109
|
+
for (let i = 1; i < blocks.length; i++) {
|
|
110
|
+
const position = offsetToPosition(raw, blocks[i].startOffset);
|
|
111
|
+
file.message(
|
|
112
|
+
`Duplicate capture block name "${name}". This variable already exists in file.`,
|
|
113
|
+
position
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check for disallowed content outside capture blocks and comments
|
|
120
|
+
const cleaned = removeAllowedContent(raw);
|
|
121
|
+
if (hasDisallowedContent(cleaned)) {
|
|
122
|
+
// Find the first line with disallowed content for better error reporting
|
|
123
|
+
const lines = cleaned.split('\n');
|
|
124
|
+
for (const line of lines) {
|
|
125
|
+
if (/\S/.test(line)) {
|
|
126
|
+
file.message(
|
|
127
|
+
`Content in shared/partials files must be inside capture blocks. Found content outside capture blocks: "${line.trim().substring(0, 50)}..."`
|
|
128
|
+
);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export = rule(SOURCE, checkLiquidCapture);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"program":{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/@types/unist/index.d.ts","../../node_modules/vfile-message/types/index.d.ts","../../node_modules/vfile/types/index.d.ts","../liquid-lint-utils/dist/parse-frontmatter.d.ts","../liquid-lint-utils/dist/disabled-tags.d.ts","../liquid-lint-utils/dist/is-liquid-enabled.d.ts","../liquid-lint-utils/dist/offset-to-position.d.ts","../liquid-lint-utils/dist/protect-code-blocks.d.ts","../liquid-lint-utils/dist/resolve-roots.d.ts","../liquid-lint-utils/dist/index.d.ts","./src/index.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostic_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/util/types.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/globals.global.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/ts3.6/base.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/base.d.ts","./node_modules/@types/node/index.d.ts","../../node_modules/@babel/types/lib/index.d.ts","../../node_modules/@types/babel__generator/index.d.ts","../../node_modules/@babel/parser/typings/babel-parser.d.ts","../../node_modules/@types/babel__template/index.d.ts","../../node_modules/@types/babel__traverse/index.d.ts","../../node_modules/@types/babel__core/index.d.ts","../../node_modules/keyv/src/index.d.ts","../../node_modules/@types/http-cache-semantics/index.d.ts","../../node_modules/@types/responselike/index.d.ts","../../node_modules/@types/cacheable-request/index.d.ts","../../node_modules/@types/dedent/index.d.ts","../../node_modules/@types/graceful-fs/index.d.ts","../../node_modules/@types/istanbul-lib-coverage/index.d.ts","../../node_modules/@types/istanbul-lib-report/index.d.ts","../../node_modules/@types/istanbul-reports/index.d.ts","../../node_modules/@types/jest/node_modules/jest-diff/build/cleanupsemantic.d.ts","../../node_modules/@types/jest/node_modules/jest-diff/build/types.d.ts","../../node_modules/@types/jest/node_modules/jest-diff/build/difflines.d.ts","../../node_modules/@types/jest/node_modules/jest-diff/build/printdiffs.d.ts","../../node_modules/@types/jest/node_modules/jest-diff/build/index.d.ts","../../node_modules/@types/jest/node_modules/pretty-format/build/types.d.ts","../../node_modules/@types/jest/node_modules/pretty-format/build/index.d.ts","../../node_modules/@types/jest/index.d.ts","../../node_modules/@types/jest/ts3.2/index.d.ts","../../node_modules/@types/js-yaml/index.d.ts","../../node_modules/@types/json-schema/index.d.ts","../../node_modules/@types/jsonpath-plus/index.d.ts","../../node_modules/@types/keyv/index.d.ts","../../node_modules/@types/mdast/index.d.ts","../../node_modules/@types/minimatch/index.d.ts","../../node_modules/@types/minimist/index.d.ts","../../node_modules/@types/mock-fs/lib/item.d.ts","../../node_modules/@types/mock-fs/lib/file.d.ts","../../node_modules/@types/mock-fs/lib/directory.d.ts","../../node_modules/@types/mock-fs/lib/symlink.d.ts","../../node_modules/@types/mock-fs/lib/filesystem.d.ts","../../node_modules/@types/mock-fs/index.d.ts","../../node_modules/@types/normalize-package-data/index.d.ts","../../node_modules/@types/prettier/index.d.ts","../../node_modules/@types/stack-utils/index.d.ts","../../node_modules/@types/yargs-parser/index.d.ts","../../node_modules/@types/yargs/index.d.ts"],"fileInfos":[{"version":"8730f4bf322026ff5229336391a18bcaa1f94d4f82416c8b2f3954e2ccaae2ba","affectsGlobalScope":true},"dc47c4fa66b9b9890cf076304de2a9c5201e94b740cffdf09f87296d877d71f6","7a387c58583dfca701b6c85e0adaf43fb17d590fb16d5b2dc0a2fbd89f35c467","8a12173c586e95f4433e0c6dc446bc88346be73ffe9ca6eec7aa63c8f3dca7f9","5f4e733ced4e129482ae2186aae29fde948ab7182844c3a5a51dd346182c7b06","4b421cbfb3a38a27c279dec1e9112c3d1da296f77a1a85ddadf7e7a425d45d18",{"version":"adb996790133eb33b33aadb9c09f15c2c575e71fb57a62de8bf74dbf59ec7dfb","affectsGlobalScope":true},{"version":"8cc8c5a3bac513368b0157f3d8b31cfdcfe78b56d3724f30f80ed9715e404af8","affectsGlobalScope":true},{"version":"cdccba9a388c2ee3fd6ad4018c640a471a6c060e96f1232062223063b0a5ac6a","affectsGlobalScope":true},{"version":"c5c05907c02476e4bde6b7e76a79ffcd948aedd14b6a8f56e4674221b0417398","affectsGlobalScope":true},{"version":"5f406584aef28a331c36523df688ca3650288d14f39c5d2e555c95f0d2ff8f6f","affectsGlobalScope":true},{"version":"22f230e544b35349cfb3bd9110b6ef37b41c6d6c43c3314a31bd0d9652fcec72","affectsGlobalScope":true},{"version":"7ea0b55f6b315cf9ac2ad622b0a7813315bb6e97bf4bb3fbf8f8affbca7dc695","affectsGlobalScope":true},{"version":"3013574108c36fd3aaca79764002b3717da09725a36a6fc02eac386593110f93","affectsGlobalScope":true},{"version":"eb26de841c52236d8222f87e9e6a235332e0788af8c87a71e9e210314300410a","affectsGlobalScope":true},{"version":"3be5a1453daa63e031d266bf342f3943603873d890ab8b9ada95e22389389006","affectsGlobalScope":true},{"version":"17bb1fc99591b00515502d264fa55dc8370c45c5298f4a5c2083557dccba5a2a","affectsGlobalScope":true},{"version":"7ce9f0bde3307ca1f944119f6365f2d776d281a393b576a18a2f2893a2d75c98","affectsGlobalScope":true},{"version":"6a6b173e739a6a99629a8594bfb294cc7329bfb7b227f12e1f7c11bc163b8577","affectsGlobalScope":true},{"version":"81cac4cbc92c0c839c70f8ffb94eb61e2d32dc1c3cf6d95844ca099463cf37ea","affectsGlobalScope":true},{"version":"b0124885ef82641903d232172577f2ceb5d3e60aed4da1153bab4221e1f6dd4e","affectsGlobalScope":true},{"version":"0eb85d6c590b0d577919a79e0084fa1744c1beba6fd0d4e951432fa1ede5510a","affectsGlobalScope":true},{"version":"da233fc1c8a377ba9e0bed690a73c290d843c2c3d23a7bd7ec5cd3d7d73ba1e0","affectsGlobalScope":true},{"version":"d154ea5bb7f7f9001ed9153e876b2d5b8f5c2bb9ec02b3ae0d239ec769f1f2ae","affectsGlobalScope":true},{"version":"bb2d3fb05a1d2ffbca947cc7cbc95d23e1d053d6595391bd325deb265a18d36c","affectsGlobalScope":true},{"version":"c80df75850fea5caa2afe43b9949338ce4e2de086f91713e9af1a06f973872b8","affectsGlobalScope":true},{"version":"9d57b2b5d15838ed094aa9ff1299eecef40b190722eb619bac4616657a05f951","affectsGlobalScope":true},{"version":"6c51b5dd26a2c31dbf37f00cfc32b2aa6a92e19c995aefb5b97a3a64f1ac99de","affectsGlobalScope":true},{"version":"6e7997ef61de3132e4d4b2250e75343f487903ddf5370e7ce33cf1b9db9a63ed","affectsGlobalScope":true},{"version":"2ad234885a4240522efccd77de6c7d99eecf9b4de0914adb9a35c0c22433f993","affectsGlobalScope":true},{"version":"5e5e095c4470c8bab227dbbc61374878ecead104c74ab9960d3adcccfee23205","affectsGlobalScope":true},{"version":"09aa50414b80c023553090e2f53827f007a301bc34b0495bfb2c3c08ab9ad1eb","affectsGlobalScope":true},{"version":"2768ef564cfc0689a1b76106c421a2909bdff0acbe87da010785adab80efdd5c","affectsGlobalScope":true},{"version":"52d1bb7ab7a3306fd0375c8bff560feed26ed676a5b0457fa8027b563aecb9a4","affectsGlobalScope":true},"6d09838b65c3c780513878793fc394ae29b8595d9e4729246d14ce69abc71140","eb09cf44043f7d6e0208dca6f0555207015e91fff5ff77b9c21d63672f7d68d5","bbf6f246061b92bb84241897eebfcdb9ce28444ab6acbc32c425388dd27c1011","55fb7da9a36a3d45502aa14b33c55e4f3baa96567978c189a7d6e4dbbfe6cd2d","36300e1494e25976af66784b85d7bf45afd4d5c71162db0350a603ae0e9e20c8","d4ffd5feb926ecddba6ff81487d9c7d6f4c759620f5145ceea6189671b6fe25d","a08c13d8c556d1c14bcd59d91e87b7e4693979db65c600ff33a069d4aa2df53e","3078a8fa4448f802b3a522b8170be54b14bf3563ac8904504a3304777b1540ff","9860ced5ed4a2a5cd4b994721d82efdf0ec1ae8b6c722a7aa95b0087ac3732aa","9811cff809a704fa631574fcce9c1a0803fb4d3c448997c44c71974b2f2a828c",{"version":"f4c5a4acf2d5301f5d6fe1aa5f2b0fc95a7d07b41832f5975a3228c912e78a9b","signature":"f7d3734419310acc0d81e9ff6dfdec2afdf4910c0c46656b45f5638286b89824"},"c7bdc99177a2a94d25fb13722adaaf5b3291bf70b4d1b27584ba189dd3889ba3",{"version":"d1c92b66c4105659fcad4eb76a1481f7311033e117d0678a1ec545e8ddb8d4c6","affectsGlobalScope":true},"e23424b97418eca3226fd24de079f1203eb70360622e4e093af2aff14d4be6ec","dee93c07b4df5e26010dc9ec4cdf4d6e4076bb4474d2a8371529217c8b2740a4","ed40f2f184db052dc8df62d1f199823c8bbccc487c0a2a7c54eeea0badcf4378","04eaa93bd75f937f9184dcb95a7983800c5770cf8ddd8ac0f3734dc02f5b20ef",{"version":"c8155caf28fc7b0a564156a5df28ad8a844a3bd32d331d148d8f3ce88025c870","affectsGlobalScope":true},"45ac321f2e15d268fd74a90ddaa6467dcaaff2c5b13f95b4b85831520fb7a491","dfc747ab8dd5f623055a4c26fd35e8cceca869fd3f1cf09701c941ca3679665a","c9f5f2920ff61d7158417b8440d5181ddc34a3dfef811a5677dd8a9fb91471e9","5cc0a492da3602510b8f5ed1852b1e0390002780d8758fbc8c0cd023ca7085f8","ec7dafafe751a5121f8f1c80201ebe7e7238c47e6329280a73c4d1ca4bb7fa28","64debeb10e4b7ae4ec9e89bfb4e04c6101ab98c3cc806d14e5488607cfec2753",{"version":"2866a528b2708aa272ec3eaafd3c980abb23aec1ef831cfc5eb2186b98c37ce5","affectsGlobalScope":true},{"version":"a5782d6cea81fe43d2db7ed41e789458c933ab3ab60602f7b5b14c4da3370496","affectsGlobalScope":true},"f258ba66915f0196ec344bc53afe1888240bbcc855ebd151b6cc072275533319","6194335ee3353f7226ba31f31d6301d0c6be87228419c0a08976ccd9d4f1213e","3ac12a54cfaa84683506db8d4cf779135a271d9f0ec18930cf53e61fbeea0c5d","cf3d3b087d1a8a3355eec47d206162c75e912315b9b5c1cd49fda93e948fb80a","36f316c066c4a72dd6f19fec49a074f935744fc9ccbe75c87ebc07fb2feb9062","42176966283d3835c34278b9b5c0f470d484c0c0c6a55c20a2c916a1ce69b6e8","0cff7901aedfe78e314f7d44088f07e2afa1b6e4f0473a4169b8456ca2fb245d","ec70fd6f8a9a83f850dab2960a6789e93d5b034b354a16814cad5daabf62a360","e2236264a811ed1d09a2487a433e8f5216ae62378cf233954ae220cf886f6717","3ec1e108d587a5661ec790db607f482605ba9f3830e118ce578e3ffa3c42e22b","100b3bb9d39d2b1b5340f1bf45a52e94ef1692b45232b4ba00fac5c3cc56d331",{"version":"04fe7e7d8008887943260af1fde2bfd4877e0dc57bf634e0f0b2f3d1794dfd11","affectsGlobalScope":true},"7f77304372efe3c9967e5f9ea2061f1b4bf41dc3cda3c83cdd676f2e5af6b7e6","992c6f6be16c0a1d2eec13ece33adeea2c747ba27fcd078353a8f4bb5b4fea58","2597718d91e306949d89e285bf34c44192014ef541c3bd7cbb825c022749e974","a6b0abdb67d63ebe964ba5fee31bc3daf10c12eecd46b24d778426010c04c67e","ac4801ebc2355ba32329070123b1cd15891bf71b41dcaf9e75b4744832126a59","fd2298fba0640e7295e7bd545e2dfbfcccbb00c27019e501c87965a02bbdebf6","4fd3c4debadce3e9ab9dec3eb45f7f5e2e3d4ad65cf975a6d938d883cfb25a50","71ddd49185b68f27bfac127ef5d22cb2672c278e53e5370d9020ef50ca9c377d","b1ea7a6eaa7608e0e0529aebd323b526a79c6c05a4e519ae5c779675004dcdf1","9fcb033a6208485d8f3fadde31eb5cbcaf99149cff3e40c0dc53ebc6d0dff4e9","7df562288f949945cf69c21cd912100c2afedeeb7cdb219085f7f4b46cb7dde4","9d16690485ff1eb4f6fc57aebe237728fd8e03130c460919da3a35f4d9bd97f5","dcc6910d95a3625fd2b0487fda055988e46ab46c357a1b3618c27b4a8dd739c9","f4149f1aa299474c7040a35fe8f8ac2ad078cc1b190415adc1fff3ed52d490ea","3730099ed008776216268a97771de31753ef71e0a7d0ec650f588cba2a06ce44","8d649dbc429d7139a1d9a14ea2bf8af1dc089e0a879447539587463d4b6c248c","60c9e27816ec8ac8df7240598bb086e95b47edfb454c5cbf4003c812e0ed6e39","e361aecf17fc4034b4c122a1564471cdcd22ef3a51407803cb5a5fc020c04d02","4926467de88a92a4fc9971d8c6f21b91eca1c0e7fc2a46cc4638ab9440c73875",{"version":"2708349d5a11a5c2e5f3a0765259ebe7ee00cdcc8161cb9990cb4910328442a1","affectsGlobalScope":true},"fc0ae4a8ad3c762b96f9d2c3700cb879a373458cb0bf3175478e3b4f85f7ef2f","fabbec378e1ddd86fcf2662e716c2b8318acedb664ee3a4cba6f9e8ee8269cf1","b3593bd345ebea5e4d0a894c03251a3774b34df3d6db57075c18e089a599ba76","e61a21e9418f279bc480394a94d1581b2dee73747adcbdef999b6737e34d721b","efd55e8ca79171bf26c0d0e30221cb8ee1f5a31dd0c791ec4b2e886f42bab124","c2c2a861a338244d7dd700d0c52a78916b4bb75b98fc8ca5e7c501899fc03796","b6d03c9cfe2cf0ba4c673c209fcd7c46c815b2619fd2aad59fc4229aaef2ed43","adb467429462e3891de5bb4a82a4189b92005d61c7f9367c089baf03997c104e","670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","13b77ab19ef7aadd86a1e54f2f08ea23a6d74e102909e3c00d31f231ed040f62","069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","42baf4ca38c38deaf411ea73f37bc39ff56c6e5c761a968b64ac1b25c92b5cd8","d7dbe0ad36bdca8a6ecf143422a48e72cc8927bab7b23a1a2485c2f78a7022c6","8718fa41d7cf4aa91de4e8f164c90f88e0bf343aa92a1b9b725a9c675c64e16b","f992cd6cc0bcbaa4e6c810468c90f2d8595f8c6c3cf050c806397d3de8585562","3189093b3543e545bcf0e6d387d60c5cfe5a224b104d15b1639c0c4db9ecf012","afe73051ff6a03a9565cbd8ebb0e956ee3df5e913ad5c1ded64218aabfa3dcb5","035a5df183489c2e22f3cf59fc1ed2b043d27f357eecc0eb8d8e840059d44245","a4809f4d92317535e6b22b01019437030077a76fec1d93b9881c9ed4738fcc54","5f53fa0bd22096d2a78533f94e02c899143b8f0f9891a46965294ee8b91a9434","e222104af6cb9415238ad358488b74d76eceeff238c1268ec6e85655b05341da","69da61a7b5093dac77fa3bec8be95dcf9a74c95a0e9161edb98bb24e30e439d2","eba230221317c985ab1953ccc3edc517f248b37db4fef7875cb2c8d08aff7be7","b83e796810e475da3564c6515bc0ae9577070596a33d89299b7d99f94ecfd921","b4439890c168d646357928431100daac5cbdee1d345a34e6bf6eca9f3abe22bc","5d72971a459517c44c1379dab9ed248e87a61ba0a1e0f25c9d67e1e640cd9a09","02d734976af36f4273d930bea88b3e62adf6b078cf120c1c63d49aa8d8427c5c",{"version":"f624e578325b8c58e55b30c998b1f4c3ec1b61a9fa66373da4250c89b7880d44","affectsGlobalScope":true},{"version":"d3002f620eab4bf6476c9da5c0efb2041d46f7df8b3032a5631bd206abef2c75","affectsGlobalScope":true},"7a1dd1e9c8bf5e23129495b10718b280340c7500570e0cfe5cffcdee51e13e48","f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","9bd81cfc874a2d896326e6d9e7a6963677389a8269b1d032663083b253e21162","fec943fdb3275eb6e006b35e04a8e2e99e9adf3f4b969ddf15315ac7575a93e4","202f8582ee3cd89e06c4a17d8aabb925ff8550370559c771d1cc3ec3934071c2","8841e2aa774b89bd23302dede20663306dc1b9902431ac64b24be8b8d0e3f649","fbca5ffaebf282ec3cdac47b0d1d4a138a8b0bb32105251a38acb235087d3318","b95f2a78de34a873c6dd76dc538b7a5fec77da6a0e0e7efc7aa58f58ddfce270","4b50f58fcf29daaeab0c583da58ee9731a4d5c003f99fd833d91ad99f19a82c3","6692416887d66b903aea54b32163d34318300772cd2b8c7c36f972937a62de74","3e640a0056177a4ccfb819d81b21c9ed0ac0e77d8b7bf580894a3392298a890b","b75cb207f8dfade4120ba3554c5781005c9de85723c76588befd47693342ca50","693c4c9acf5e1c56e30130a90e07bd05122e6194d69018b0b20c6581c1324e0a","22293bd6fa12747929f8dfca3ec1684a3fe08638aa18023dd286ab337e88a592","d88a5e779faf033be3d52142a04fbe1cb96009868e3bbdd296b2bc6c59e06c0e","ab82804a14454734010dcdcd43f564ff7b0389bee4c5692eec76ff5b30d4cf66","bae8d023ef6b23df7da26f51cea44321f95817c190342a36882e93b80d07a960","ae271d475b632ce7b03fea6d9cf6da72439e57a109672671cbc79f54e1386938"],"options":{"composite":true,"declaration":true,"declarationMap":true,"esModuleInterop":true,"experimentalDecorators":true,"module":1,"noUnusedLocals":true,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strict":false,"target":6},"fileIdsList":[[98],[98,99,100,101,102],[98,100],[60,63,85,97,104,105,106],[61,97],[110],[111],[117,119],[113,114],[113,114,115,116],[118],[120],[60,97],[35],[130,131,132,133],[129],[97,129],[129,130,131,132],[63,77,97],[138],[60],[35,36],[95],[94,95],[49,54],[60,61,68,77],[50,60,68],[86],[54,61,69],[77,82],[57,60,68],[58],[57],[60,62,77,85],[60,61,77],[68,77,85],[60,61,63,68,77,82,85],[63,77,82,85],[96],[85],[57,60,77],[70],[48],[84],[75,86,89],[60,78],[77],[80],[54,68],[46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93],[68],[74],[87],[49,54,60,62,71,77,85,89],[37,44],[38,39,40,41,42,43]],"referencedMap":[[100,1],[103,2],[99,1],[101,3],[102,1],[107,4],[109,5],[111,6],[112,7],[120,8],[115,9],[117,10],[116,9],[119,11],[121,12],[125,13],[126,14],[134,15],[131,16],[130,17],[133,18],[132,16],[106,19],[139,20],[104,21],[36,14],[37,22],[46,23],[96,24],[49,25],[50,26],[51,27],[52,28],[53,29],[54,30],[55,31],[57,32],[58,33],[59,21],[60,21],[61,34],[62,35],[63,36],[64,37],[65,38],[97,39],[66,21],[67,40],[68,41],[70,42],[71,43],[72,44],[75,21],[76,45],[77,46],[78,47],[80,21],[81,48],[82,49],[94,50],[84,51],[85,52],[86,53],[88,47],[90,54],[91,47],[45,55],[44,56]],"exportedModulesMap":[[100,1],[103,2],[99,1],[101,3],[102,1],[107,4],[109,5],[111,6],[112,7],[120,8],[115,9],[117,10],[116,9],[119,11],[121,12],[125,13],[126,14],[134,15],[131,16],[130,17],[133,18],[132,16],[106,19],[139,20],[104,21],[36,14],[37,22],[46,23],[96,24],[49,25],[50,26],[51,27],[52,28],[53,29],[54,30],[55,31],[57,32],[58,33],[59,21],[60,21],[61,34],[62,35],[63,36],[64,37],[65,38],[97,39],[66,21],[67,40],[68,41],[70,42],[71,43],[72,44],[75,21],[76,45],[77,46],[78,47],[80,21],[81,48],[82,49],[94,50],[84,51],[85,52],[86,53],[88,47],[90,54],[91,47],[44,56]],"semanticDiagnosticsPerFile":[100,98,103,99,101,102,107,108,109,105,110,111,112,120,113,115,117,116,114,119,118,121,122,123,124,125,126,127,128,134,131,130,133,129,132,135,136,106,137,35,138,139,104,8,7,2,9,10,11,12,13,14,15,16,3,4,20,17,18,19,21,22,23,5,24,25,26,27,6,31,28,29,30,32,33,1,34,36,37,95,46,48,96,49,50,51,52,53,54,55,56,57,58,59,60,61,62,47,92,63,64,65,97,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,94,84,85,86,87,88,89,93,90,91,45,39,44,40,41,38,42,43],"latestChangedDtsFile":"./dist/index.d.ts"},"version":"4.9.5"}
|