@salesforcedevs/sfdocs-liquid-lint-capture 0.0.4-alpha → 0.0.5-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.
@@ -11,19 +11,22 @@ const processMarkdown = (md: string, filePath: string): VFile => {
11
11
  .processSync(file);
12
12
  };
13
13
 
14
- describe('sfdocs-liquid-lint:partials-capture', () => {
15
- describe('file path filtering', () => {
16
- test('ignores regular content in files not in shared/partials folder', () => {
14
+ describe('sfdocs-liquid-lint:liquid-capture-outside-partials', () => {
15
+ describe('files outside shared/partials', () => {
16
+ test('ignores regular content without capture blocks', () => {
17
17
  const md = dedent(`
18
18
  # This is a regular heading
19
19
 
20
20
  Some content here.
21
+
22
+ - List item
23
+ - Another item
21
24
  `);
22
25
  const result = processMarkdown(md, '/content/guides/example.md');
23
26
  expect(result.messages).toHaveLength(0);
24
27
  });
25
28
 
26
- test('disallows capture blocks in files outside shared/partials folder', () => {
29
+ test('warns when capture block is found outside shared/partials', () => {
27
30
  const md = dedent(`
28
31
  # This is a regular heading
29
32
 
@@ -32,25 +35,27 @@ describe('sfdocs-liquid-lint:partials-capture', () => {
32
35
  {% endcapture %}
33
36
  `);
34
37
  const result = processMarkdown(md, '/content/guides/example.md');
38
+
35
39
  expect(result.messages).toHaveLength(1);
36
40
  expect(result.messages[0].message).toContain('Capture blocks are not allowed in files outside shared/partials folder');
37
41
  expect(result.messages[0].message).toContain('my_var');
38
42
  });
39
43
 
40
- test('reports all capture blocks in non-partials files', () => {
44
+ test('warns for all capture blocks in non-partials files', () => {
41
45
  const md = dedent(`
42
46
  {% capture var1 %}Content 1{% endcapture %}
43
47
  {% capture var2 %}Content 2{% endcapture %}
44
48
  {% capture var3 %}Content 3{% endcapture %}
45
49
  `);
46
50
  const result = processMarkdown(md, '/docs/guides/example.md');
51
+
47
52
  expect(result.messages).toHaveLength(3);
48
53
  expect(result.messages[0].message).toContain('var1');
49
54
  expect(result.messages[1].message).toContain('var2');
50
55
  expect(result.messages[2].message).toContain('var3');
51
56
  });
52
57
 
53
- test('allows normal content but disallows only captures in non-partials', () => {
58
+ test('warns for captures mixed with normal content', () => {
54
59
  const md = dedent(`
55
60
  # Normal Heading
56
61
 
@@ -59,20 +64,21 @@ describe('sfdocs-liquid-lint:partials-capture', () => {
59
64
  - List item 1
60
65
  - List item 2
61
66
 
62
- {% capture should_fail %}Captured content{% endcapture %}
67
+ {% capture should_warn %}Captured content{% endcapture %}
63
68
 
64
69
  More normal content here.
65
70
 
66
- {% capture also_fails %}Another capture{% endcapture %}
71
+ {% capture also_warns %}Another capture{% endcapture %}
67
72
  `);
68
73
  const result = processMarkdown(md, '/docs/guide.md');
74
+
69
75
  expect(result.messages).toHaveLength(2);
70
76
  expect(result.messages[0].message).toContain('not allowed in files outside shared/partials');
71
- expect(result.messages[0].message).toContain('should_fail');
72
- expect(result.messages[1].message).toContain('also_fails');
77
+ expect(result.messages[0].message).toContain('should_warn');
78
+ expect(result.messages[1].message).toContain('also_warns');
73
79
  });
74
80
 
75
- test('reports correct line positions for captures in non-partials', () => {
81
+ test('reports correct line numbers for captures', () => {
76
82
  const md = dedent(`
77
83
  # Title
78
84
 
@@ -83,33 +89,76 @@ describe('sfdocs-liquid-lint:partials-capture', () => {
83
89
  {% capture var2 %}Second{% endcapture %}
84
90
  `);
85
91
  const result = processMarkdown(md, '/docs/page.md');
92
+
86
93
  expect(result.messages).toHaveLength(2);
87
94
  expect(result.messages[0].line).toBe(3);
88
95
  expect(result.messages[1].line).toBe(7);
89
96
  });
90
97
 
91
- test('validates files in shared/partials folder', () => {
98
+ test('warns for capture blocks with whitespace control', () => {
99
+ const md = '{%- capture my_var -%}Content{%- endcapture -%}';
100
+ const result = processMarkdown(md, '/content/docs/page.md');
101
+
102
+ expect(result.messages).toHaveLength(1);
103
+ expect(result.messages[0].message).toContain('not allowed in files outside shared/partials');
104
+ expect(result.messages[0].message).toContain('my_var');
105
+ });
106
+
107
+ test('warns for captures in various non-partials directories', () => {
108
+ const md = '{% capture test %}Content{% endcapture %}';
109
+ const paths = [
110
+ '/content/guides/intro.md',
111
+ '/docs/api/endpoints.md',
112
+ '/tutorials/getting-started.md',
113
+ '/content/shared/components/card.md', // Not in partials
114
+ ];
115
+
116
+ paths.forEach(path => {
117
+ const result = processMarkdown(md, path);
118
+ expect(result.messages).toHaveLength(1);
119
+ expect(result.messages[0].message).toContain('not allowed in files outside shared/partials');
120
+ });
121
+ });
122
+
123
+ test('handles multiline capture content', () => {
92
124
  const md = dedent(`
93
- # This is a heading
125
+ {% capture multiline %}
126
+ Line 1
127
+ Line 2
128
+ Line 3
129
+ {% endcapture %}
94
130
  `);
95
- const result = processMarkdown(md, '/content/shared/partials/example.md');
96
- expect(result.messages.length).toBeGreaterThan(0);
97
- expect(result.messages[0].message).toContain('must be inside capture blocks');
131
+ const result = processMarkdown(md, '/content/tutorial.md');
132
+
133
+ expect(result.messages).toHaveLength(1);
134
+ expect(result.messages[0].message).toContain('multiline');
135
+ });
136
+
137
+ test('handles variable names with underscores and numbers', () => {
138
+ const md = dedent(`
139
+ {% capture my_var_123 %}Content{% endcapture %}
140
+ {% capture another_var_456 %}More content{% endcapture %}
141
+ `);
142
+ const result = processMarkdown(md, '/docs/test.md');
143
+
144
+ expect(result.messages).toHaveLength(2);
145
+ expect(result.messages[0].message).toContain('my_var_123');
146
+ expect(result.messages[1].message).toContain('another_var_456');
98
147
  });
99
148
  });
100
149
 
101
- describe('valid capture blocks in partials', () => {
102
- test('allows single capture block', () => {
150
+ describe('files inside shared/partials', () => {
151
+ test('allows capture blocks in shared/partials folder', () => {
103
152
  const md = dedent(`
104
153
  {% capture my_variable %}
105
154
  This is captured content.
106
155
  {% endcapture %}
107
156
  `);
108
- const result = processMarkdown(md, '/shared/partials/example.md');
157
+ const result = processMarkdown(md, '/content/shared/partials/example.md');
109
158
  expect(result.messages).toHaveLength(0);
110
159
  });
111
160
 
112
- test('allows multiple capture blocks with different names', () => {
161
+ test('allows multiple capture blocks in partials', () => {
113
162
  const md = dedent(`
114
163
  {% capture var1 %}
115
164
  Content 1
@@ -127,7 +176,7 @@ describe('sfdocs-liquid-lint:partials-capture', () => {
127
176
  expect(result.messages).toHaveLength(0);
128
177
  });
129
178
 
130
- test('allows capture blocks with whitespace control', () => {
179
+ test('allows capture blocks with whitespace control in partials', () => {
131
180
  const md = dedent(`
132
181
  {%- capture my_variable -%}
133
182
  Trimmed content
@@ -137,310 +186,178 @@ describe('sfdocs-liquid-lint:partials-capture', () => {
137
186
  expect(result.messages).toHaveLength(0);
138
187
  });
139
188
 
140
- test('disallows whitespace-controlled captures in non-partials', () => {
141
- const md = '{%- capture my_var -%}Content{%- endcapture -%}';
142
- const result = processMarkdown(md, '/content/docs/page.md');
143
- expect(result.messages).toHaveLength(1);
144
- expect(result.messages[0].message).toContain('not allowed in files outside shared/partials');
145
- expect(result.messages[0].message).toContain('my_var');
146
- });
147
-
148
- test('allows capture blocks with markdown content inside', () => {
149
- const md = dedent(`
150
- {% capture rich_content %}
151
- # Heading inside capture
189
+ test('allows captures in various partials paths', () => {
190
+ const md = '{% capture test %}Content{% endcapture %}';
191
+ const paths = [
192
+ '/content/shared/partials/banner.md',
193
+ '/docs/shared/partials/footer.md',
194
+ '/api/v1/shared/partials/header.md',
195
+ ];
152
196
 
153
- This is a paragraph with **bold** and *italic*.
154
-
155
- - List item 1
156
- - List item 2
157
- {% endcapture %}
158
- `);
159
- const result = processMarkdown(md, '/shared/partials/example.md');
160
- expect(result.messages).toHaveLength(0);
197
+ paths.forEach(path => {
198
+ const result = processMarkdown(md, path);
199
+ expect(result.messages).toHaveLength(0);
200
+ });
161
201
  });
162
- });
163
202
 
164
- describe('comments', () => {
165
- test('allows HTML comments', () => {
203
+ test('allows regular content in partials', () => {
166
204
  const md = dedent(`
167
- <!-- This is an HTML comment -->
168
- {% capture my_var %}Content{% endcapture %}
169
- `);
170
- const result = processMarkdown(md, '/shared/partials/example.md');
171
- expect(result.messages).toHaveLength(0);
172
- });
173
-
174
- test('allows multi-line HTML comments', () => {
175
- const md = dedent(`
176
- <!--
177
- This is a multi-line
178
- HTML comment
179
- -->
180
- {% capture my_var %}Content{% endcapture %}
181
- `);
182
- const result = processMarkdown(md, '/shared/partials/example.md');
183
- expect(result.messages).toHaveLength(0);
184
- });
205
+ # Heading in partials
185
206
 
186
- test('allows Liquid comment blocks', () => {
187
- const md = dedent(`
188
- {% comment %}
189
- This is a Liquid comment
190
- {% endcomment %}
191
- {% capture my_var %}Content{% endcapture %}
207
+ Regular content is allowed here.
192
208
  `);
193
- const result = processMarkdown(md, '/shared/partials/example.md');
209
+ const result = processMarkdown(md, '/content/shared/partials/test.md');
194
210
  expect(result.messages).toHaveLength(0);
195
211
  });
196
212
 
197
- test('allows Liquid comments with whitespace control', () => {
198
- const md = dedent(`
199
- {%- comment -%}
200
- Trimmed comment
201
- {%- endcomment -%}
202
- {% capture my_var %}Content{% endcapture %}
203
- `);
204
- const result = processMarkdown(md, '/shared/partials/example.md');
213
+ test('allows empty files in partials', () => {
214
+ const result = processMarkdown('', '/content/shared/partials/empty.md');
205
215
  expect(result.messages).toHaveLength(0);
206
216
  });
207
217
 
208
- test('allows mix of HTML and Liquid comments', () => {
218
+ test('allows capture blocks with markdown content inside', () => {
209
219
  const md = dedent(`
210
- <!-- HTML comment -->
211
- {% comment %}Liquid comment{% endcomment %}
212
- {% capture my_var %}Content{% endcapture %}
213
- <!-- Another HTML comment -->
214
- `);
215
- const result = processMarkdown(md, '/shared/partials/example.md');
216
- expect(result.messages).toHaveLength(0);
217
- });
218
- });
220
+ {% capture rich_content %}
221
+ # Heading
219
222
 
220
- describe('duplicate capture names', () => {
221
- test('reports duplicate capture block names', () => {
222
- const md = dedent(`
223
- {% capture my_variable %}
224
- First capture
225
- {% endcapture %}
223
+ **Bold text** and *italic*.
226
224
 
227
- {% capture my_variable %}
228
- Duplicate capture
225
+ - List item
229
226
  {% endcapture %}
230
227
  `);
231
- const result = processMarkdown(md, '/shared/partials/example.md');
232
- expect(result.messages).toHaveLength(1);
233
- expect(result.messages[0].message).toContain('Duplicate capture block name "my_variable"');
234
- });
235
-
236
- test('reports multiple duplicates', () => {
237
- const md = dedent(`
238
- {% capture var1 %}First{% endcapture %}
239
- {% capture var2 %}First{% endcapture %}
240
- {% capture var1 %}Duplicate 1{% endcapture %}
241
- {% capture var2 %}Duplicate 2{% endcapture %}
242
- `);
243
- const result = processMarkdown(md, '/shared/partials/example.md');
244
- expect(result.messages).toHaveLength(2);
245
- expect(result.messages[0].message).toContain('Duplicate capture block name "var1"');
246
- expect(result.messages[1].message).toContain('Duplicate capture block name "var2"');
247
- });
248
-
249
- test('reports all occurrences after the first', () => {
250
- const md = dedent(`
251
- {% capture my_var %}First{% endcapture %}
252
- {% capture my_var %}Second{% endcapture %}
253
- {% capture my_var %}Third{% endcapture %}
254
- `);
255
- const result = processMarkdown(md, '/shared/partials/example.md');
256
- expect(result.messages).toHaveLength(2);
228
+ const result = processMarkdown(md, '/content/shared/partials/rich.md');
229
+ expect(result.messages).toHaveLength(0);
257
230
  });
258
231
  });
259
232
 
260
- describe('disallowed content', () => {
261
- test('reports markdown heading outside capture', () => {
262
- const md = dedent(`
263
- # This is a heading
264
-
265
- {% capture my_var %}Content{% endcapture %}
266
- `);
267
- const result = processMarkdown(md, '/shared/partials/example.md');
268
- expect(result.messages.length).toBeGreaterThan(0);
269
- expect(result.messages[0].message).toContain('must be inside capture blocks');
270
- });
271
-
272
- test('reports paragraph content outside capture', () => {
273
- const md = dedent(`
274
- {% capture my_var %}Captured{% endcapture %}
275
-
276
- This is regular paragraph content.
277
- `);
278
- const result = processMarkdown(md, '/shared/partials/example.md');
279
- expect(result.messages.length).toBeGreaterThan(0);
280
- expect(result.messages[0].message).toContain('must be inside capture blocks');
281
- });
282
-
283
- test('reports list outside capture', () => {
233
+ describe('code block protection', () => {
234
+ test('ignores capture blocks inside code blocks', () => {
284
235
  const md = dedent(`
285
- - List item 1
286
- - List item 2
236
+ Example code:
287
237
 
288
- {% capture my_var %}Captured{% endcapture %}
289
- `);
290
- const result = processMarkdown(md, '/shared/partials/example.md');
291
- expect(result.messages.length).toBeGreaterThan(0);
292
- expect(result.messages[0].message).toContain('must be inside capture blocks');
293
- });
294
-
295
- test('reports code block outside capture', () => {
296
- const md = dedent(`
297
- \`\`\`js
298
- const x = 1;
238
+ \`\`\`liquid
239
+ {% capture example %}
240
+ This is just example code
241
+ {% endcapture %}
299
242
  \`\`\`
300
-
301
- {% capture my_var %}Captured{% endcapture %}
302
243
  `);
303
- const result = processMarkdown(md, '/shared/partials/example.md');
304
- expect(result.messages.length).toBeGreaterThan(0);
305
- expect(result.messages[0].message).toContain('must be inside capture blocks');
244
+ const result = processMarkdown(md, '/content/docs/tutorial.md');
245
+ expect(result.messages).toHaveLength(0);
306
246
  });
307
247
 
308
- test('allows whitespace outside capture blocks', () => {
248
+ test('ignores multiple capture blocks in code blocks', () => {
309
249
  const md = dedent(`
310
-
311
-
312
- {% capture my_var %}Content{% endcapture %}
313
-
314
-
315
- {% capture another_var %}More content{% endcapture %}
316
-
317
-
250
+ \`\`\`
251
+ {% capture var1 %}Example 1{% endcapture %}
252
+ {% capture var2 %}Example 2{% endcapture %}
253
+ {% capture var3 %}Example 3{% endcapture %}
254
+ \`\`\`
318
255
  `);
319
- const result = processMarkdown(md, '/shared/partials/example.md');
256
+ const result = processMarkdown(md, '/docs/examples.md');
320
257
  expect(result.messages).toHaveLength(0);
321
258
  });
322
- });
323
259
 
324
- describe('complex scenarios', () => {
325
- test('valid file with multiple captures and comments', () => {
260
+ test('warns for real captures but ignores code block captures', () => {
326
261
  const md = dedent(`
327
- <!-- Header comment explaining this file -->
328
-
329
- {% comment %}
330
- This section defines variable 1
331
- {% endcomment %}
332
- {% capture variable_one %}
333
- # Section 1
262
+ # Documentation
334
263
 
335
- Content for variable one.
336
- {% endcapture %}
337
-
338
- <!-- Separator comment -->
264
+ Here's an example:
339
265
 
340
- {% capture variable_two %}
341
- ## Section 2
266
+ \`\`\`
267
+ {% capture demo %}Example{% endcapture %}
268
+ \`\`\`
342
269
 
343
- Content for variable two with **formatting**.
344
- {% endcapture %}
270
+ Now using it for real:
345
271
 
346
- {%- comment -%}End of file{%- endcomment -%}
272
+ {% capture real_usage %}Actual content{% endcapture %}
347
273
  `);
348
- const result = processMarkdown(md, '/shared/partials/example.md');
349
- expect(result.messages).toHaveLength(0);
350
- });
351
-
352
- test('reports both duplicate names and disallowed content', () => {
353
- const md = dedent(`
354
- # Invalid heading
274
+ const result = processMarkdown(md, '/docs/guide.md');
355
275
 
356
- {% capture my_var %}First{% endcapture %}
357
- {% capture my_var %}Duplicate{% endcapture %}
358
- `);
359
- const result = processMarkdown(md, '/shared/partials/example.md');
360
- expect(result.messages.length).toBeGreaterThan(0);
361
- // Should have both error types
362
- const messages = result.messages.map(m => m.message);
363
- expect(messages.some(m => m.includes('Duplicate capture'))).toBe(true);
364
- expect(messages.some(m => m.includes('must be inside capture blocks'))).toBe(true);
276
+ expect(result.messages).toHaveLength(1);
277
+ expect(result.messages[0].message).toContain('real_usage');
278
+ expect(result.messages[0].line).toBe(11);
365
279
  });
366
280
 
367
- test('handles nested liquid tags inside capture', () => {
281
+ test('ignores code blocks in partials too', () => {
368
282
  const md = dedent(`
369
- {% capture my_content %}
370
- This has {% if true %}conditional{% endif %} content.
371
- And a {{ variable }} reference.
372
- {% endcapture %}
283
+ \`\`\`
284
+ {% capture example %}Code example{% endcapture %}
285
+ \`\`\`
373
286
  `);
374
- const result = processMarkdown(md, '/shared/partials/example.md');
287
+ const result = processMarkdown(md, '/content/shared/partials/doc.md');
375
288
  expect(result.messages).toHaveLength(0);
376
289
  });
377
290
  });
378
291
 
379
292
  describe('edge cases', () => {
380
- test('handles empty file', () => {
381
- const md = '';
382
- const result = processMarkdown(md, '/shared/partials/example.md');
293
+ test('handles empty files', () => {
294
+ const result = processMarkdown('', '/content/docs/empty.md');
383
295
  expect(result.messages).toHaveLength(0);
384
296
  });
385
297
 
386
- test('handles file with only comments', () => {
387
- const md = dedent(`
388
- <!-- Only comments here -->
389
- {% comment %}No capture blocks{% endcomment %}
390
- `);
391
- const result = processMarkdown(md, '/shared/partials/example.md');
298
+ test('handles files with no path', () => {
299
+ const file = vfile({ contents: '{% capture foo %}test{% endcapture %}' });
300
+ const result = remark()
301
+ .use(plugin as unified.Attacher)
302
+ .processSync(file);
392
303
  expect(result.messages).toHaveLength(0);
393
304
  });
394
305
 
395
- test('handles file with only whitespace', () => {
396
- const md = ' \n\n\t\n ';
397
- const result = processMarkdown(md, '/shared/partials/example.md');
306
+ test('handles files with null contents', () => {
307
+ const file = vfile({ path: '/content/test.md', contents: null });
308
+ const result = remark()
309
+ .use(plugin as unified.Attacher)
310
+ .processSync(file);
398
311
  expect(result.messages).toHaveLength(0);
399
312
  });
400
313
 
401
- test('handles capture block with underscores and numbers in name', () => {
402
- const md = dedent(`
403
- {% capture my_var_123 %}Content{% endcapture %}
404
- `);
405
- const result = processMarkdown(md, '/shared/partials/example.md');
314
+ test('handles files with only whitespace', () => {
315
+ const md = ' \n\n \n ';
316
+ const result = processMarkdown(md, '/content/docs/whitespace.md');
406
317
  expect(result.messages).toHaveLength(0);
407
318
  });
408
319
 
409
- test('handles missing file path gracefully', () => {
410
- const file = vfile({ contents: '{% capture x %}test{% endcapture %}' });
411
- const result = remark()
412
- .use(plugin as unified.Attacher)
413
- .processSync(file);
414
- expect(result.messages).toHaveLength(0);
320
+ test('case sensitive directory matching', () => {
321
+ // Should NOT match because of case difference
322
+ const md = '{% capture test %}Content{% endcapture %}';
323
+ const result = processMarkdown(md, '/content/Shared/Partials/test.md');
324
+
325
+ // Capital S and P don't match 'shared/partials'
326
+ expect(result.messages).toHaveLength(1);
415
327
  });
328
+ });
416
329
 
417
- test('validates various path formats for partials', () => {
418
- const md = '{% capture x %}Content{% endcapture %}';
330
+ describe('mixed liquid tags', () => {
331
+ test('ignores other Liquid tags, only checks capture', () => {
332
+ const md = dedent(`
333
+ {% if condition %}
334
+ Some content
335
+ {% endif %}
419
336
 
420
- // These should all be treated as partials (valid) - must contain '/shared/partials/'
421
- expect(processMarkdown(md, '/app/shared/partials/x.md').messages).toHaveLength(0);
422
- expect(processMarkdown(md, '/shared/partials/x.md').messages).toHaveLength(0);
423
- expect(processMarkdown(md, '/content/shared/partials/alerts.md').messages).toHaveLength(0);
424
- expect(processMarkdown(md, 'docs/shared/partials/alerts.md').messages).toHaveLength(0);
337
+ {% for item in items %}
338
+ {{ item }}
339
+ {% endfor %}
340
+
341
+ {% assign var = "value" %}
342
+ `);
343
+ const result = processMarkdown(md, '/content/docs/page.md');
344
+ expect(result.messages).toHaveLength(0);
425
345
  });
426
346
 
427
- test('validates various path formats for non-partials', () => {
428
- const md = '{% capture x %}Content{% endcapture %}';
347
+ test('warns for capture but ignores other tags', () => {
348
+ const md = dedent(`
349
+ {% if condition %}
350
+ Content
351
+ {% endif %}
429
352
 
430
- // These should all be treated as non-partials (invalid)
431
- expect(processMarkdown(md, '/docs/guides/intro.md').messages).toHaveLength(1);
432
- expect(processMarkdown(md, 'content/tutorials/example.md').messages).toHaveLength(1);
433
- expect(processMarkdown(md, '/shared/snippets/code.md').messages).toHaveLength(1);
434
- expect(processMarkdown(md, 'README.md').messages).toHaveLength(1);
435
- });
353
+ {% capture my_var %}Should warn{% endcapture %}
436
354
 
437
- test('does not match similar but different paths', () => {
438
- const md = '# Heading';
355
+ {% assign foo = "bar" %}
356
+ `);
357
+ const result = processMarkdown(md, '/content/docs/page.md');
439
358
 
440
- // These paths should NOT be treated as partials
441
- expect(processMarkdown(md, '/shared-partials/x.md').messages).toHaveLength(0);
442
- expect(processMarkdown(md, '/sharedpartials/x.md').messages).toHaveLength(0);
443
- expect(processMarkdown(md, '/my-shared/partials/x.md').messages).toHaveLength(0);
359
+ expect(result.messages).toHaveLength(1);
360
+ expect(result.messages[0].message).toContain('my_var');
444
361
  });
445
362
  });
446
363
  });