@platformos/platformos-check-common 0.0.7 → 0.0.9
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/CHANGELOG.md +16 -0
- package/README.md +4 -4
- package/dist/AugmentedPlatformOSDocset.d.ts +11 -0
- package/dist/AugmentedPlatformOSDocset.js +81 -0
- package/dist/AugmentedPlatformOSDocset.js.map +1 -0
- package/dist/JSONValidator.js +1 -1
- package/dist/JSONValidator.js.map +1 -1
- package/dist/checks/deprecated-filter/index.js +4 -41
- package/dist/checks/deprecated-filter/index.js.map +1 -1
- package/dist/checks/deprecated-tag/index.js +21 -22
- package/dist/checks/deprecated-tag/index.js.map +1 -1
- package/dist/checks/duplicate-function-arguments/index.js +1 -1
- package/dist/checks/duplicate-function-arguments/index.js.map +1 -1
- package/dist/checks/duplicate-render-partial-arguments/index.js +1 -1
- package/dist/checks/duplicate-render-partial-arguments/index.js.map +1 -1
- package/dist/checks/graphql/index.js +1 -1
- package/dist/checks/graphql/index.js.map +1 -1
- package/dist/checks/graphql-variables/index.js +4 -0
- package/dist/checks/graphql-variables/index.js.map +1 -1
- package/dist/checks/img-width-and-height/index.js +1 -1
- package/dist/checks/img-width-and-height/index.js.map +1 -1
- package/dist/checks/index.d.ts +3 -3
- package/dist/checks/index.js +4 -79
- package/dist/checks/index.js.map +1 -1
- package/dist/checks/json-syntax-error/index.js +1 -1
- package/dist/checks/json-syntax-error/index.js.map +1 -1
- package/dist/checks/liquid-html-syntax-error/checks/InvalidLoopArguments.js +1 -1
- package/dist/checks/liquid-html-syntax-error/checks/InvalidLoopArguments.js.map +1 -1
- package/dist/checks/liquid-html-syntax-error/checks/InvalidTagSyntax.d.ts +19 -0
- package/dist/checks/liquid-html-syntax-error/checks/InvalidTagSyntax.js +79 -0
- package/dist/checks/liquid-html-syntax-error/checks/InvalidTagSyntax.js.map +1 -0
- package/dist/checks/liquid-html-syntax-error/checks/UnknownTag.d.ts +3 -0
- package/dist/checks/liquid-html-syntax-error/checks/UnknownTag.js +32 -0
- package/dist/checks/liquid-html-syntax-error/checks/UnknownTag.js.map +1 -0
- package/dist/checks/liquid-html-syntax-error/index.js +23 -5
- package/dist/checks/liquid-html-syntax-error/index.js.map +1 -1
- package/dist/checks/matching-translations/index.d.ts +2 -2
- package/dist/checks/matching-translations/index.js +114 -90
- package/dist/checks/matching-translations/index.js.map +1 -1
- package/dist/checks/metadata-params/index.js +6 -3
- package/dist/checks/metadata-params/index.js.map +1 -1
- package/dist/checks/missing-asset/index.js +1 -1
- package/dist/checks/missing-asset/index.js.map +1 -1
- package/dist/checks/missing-partial/index.d.ts +6 -0
- package/dist/checks/missing-partial/index.js +70 -0
- package/dist/checks/missing-partial/index.js.map +1 -0
- package/dist/checks/orphaned-partial/index.js +4 -4
- package/dist/checks/orphaned-partial/index.js.map +1 -1
- package/dist/checks/parser-blocking-script/index.js +10 -36
- package/dist/checks/parser-blocking-script/index.js.map +1 -1
- package/dist/checks/parser-blocking-script/suggestions.d.ts +1 -2
- package/dist/checks/parser-blocking-script/suggestions.js +1 -11
- package/dist/checks/parser-blocking-script/suggestions.js.map +1 -1
- package/dist/checks/reserved-doc-param-names/index.js +6 -5
- package/dist/checks/reserved-doc-param-names/index.js.map +1 -1
- package/dist/checks/translation-key-exists/index.js +1 -1
- package/dist/checks/translation-key-exists/index.js.map +1 -1
- package/dist/checks/unclosed-html-element/index.js +5 -1
- package/dist/checks/unclosed-html-element/index.js.map +1 -1
- package/dist/checks/undefined-object/index.js +13 -31
- package/dist/checks/undefined-object/index.js.map +1 -1
- package/dist/checks/unique-doc-param-names/index.js +1 -1
- package/dist/checks/unique-doc-param-names/index.js.map +1 -1
- package/dist/checks/unknown-filter/index.js +3 -3
- package/dist/checks/unknown-filter/index.js.map +1 -1
- package/dist/checks/unknown-property/index.js +1 -1
- package/dist/checks/unknown-property/index.js.map +1 -1
- package/dist/checks/unrecognized-render-partial-arguments/index.js +2 -2
- package/dist/checks/unrecognized-render-partial-arguments/index.js.map +1 -1
- package/dist/checks/unused-assign/index.js +1 -1
- package/dist/checks/unused-assign/index.js.map +1 -1
- package/dist/checks/unused-doc-param/index.js +1 -1
- package/dist/checks/unused-doc-param/index.js.map +1 -1
- package/dist/checks/utils.js +1 -1
- package/dist/checks/utils.js.map +1 -1
- package/dist/checks/valid-content-for-arguments/index.js +1 -1
- package/dist/checks/valid-content-for-arguments/index.js.map +1 -1
- package/dist/checks/valid-doc-param-types/index.js +4 -4
- package/dist/checks/valid-doc-param-types/index.js.map +1 -1
- package/dist/checks/valid-html-translation/index.d.ts +2 -2
- package/dist/checks/valid-html-translation/index.js +4 -4
- package/dist/checks/valid-html-translation/index.js.map +1 -1
- package/dist/checks/valid-json/index.js +1 -1
- package/dist/checks/valid-json/index.js.map +1 -1
- package/dist/checks/valid-render-partial-argument-types/index.js +2 -2
- package/dist/checks/valid-render-partial-argument-types/index.js.map +1 -1
- package/dist/checks/variable-name/index.js +1 -1
- package/dist/checks/variable-name/index.js.map +1 -1
- package/dist/context-utils.d.ts +18 -7
- package/dist/context-utils.js +68 -109
- package/dist/context-utils.js.map +1 -1
- package/dist/disabled-checks/index.js +4 -2
- package/dist/disabled-checks/index.js.map +1 -1
- package/dist/doc-generator/DocBlockGenerator.d.ts +16 -0
- package/dist/doc-generator/DocBlockGenerator.js +464 -0
- package/dist/doc-generator/DocBlockGenerator.js.map +1 -0
- package/dist/doc-generator/index.d.ts +1 -0
- package/dist/doc-generator/index.js +6 -0
- package/dist/doc-generator/index.js.map +1 -0
- package/dist/find-root.d.ts +7 -10
- package/dist/find-root.js +10 -17
- package/dist/find-root.js.map +1 -1
- package/dist/fixes/autofix.d.ts +4 -4
- package/dist/fixes/autofix.js +2 -2
- package/dist/fixes/autofix.js.map +1 -1
- package/dist/fixes/correctors/index.js +4 -0
- package/dist/fixes/correctors/index.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +50 -17
- package/dist/index.js.map +1 -1
- package/dist/jsonc/parse.d.ts +1 -1
- package/dist/jsonc/parse.js +1 -1
- package/dist/liquid-doc/arguments.d.ts +7 -8
- package/dist/liquid-doc/arguments.js +28 -29
- package/dist/liquid-doc/arguments.js.map +1 -1
- package/dist/liquid-doc/liquidDoc.d.ts +1 -1
- package/dist/liquid-doc/liquidDoc.js.map +1 -1
- package/dist/liquid-doc/utils.d.ts +3 -3
- package/dist/liquid-doc/utils.js +14 -3
- package/dist/liquid-doc/utils.js.map +1 -1
- package/dist/path.d.ts +1 -0
- package/dist/path.js +16 -1
- package/dist/path.js.map +1 -1
- package/{src/test/MockTheme.ts → dist/test/MockApp.d.ts} +2 -3
- package/dist/test/MockApp.js +16 -0
- package/dist/test/MockApp.js.map +1 -0
- package/dist/test/MockFileSystem.d.ts +3 -3
- package/dist/test/MockFileSystem.js +6 -6
- package/dist/test/MockFileSystem.js.map +1 -1
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +1 -1
- package/dist/test/index.js.map +1 -1
- package/dist/test/test-helper.d.ts +10 -9
- package/dist/test/test-helper.js +15 -106
- package/dist/test/test-helper.js.map +1 -1
- package/dist/to-schema.d.ts +1 -1
- package/dist/to-source-code.d.ts +3 -2
- package/dist/to-source-code.js +20 -0
- package/dist/to-source-code.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/platformos-liquid-docs.d.ts +128 -0
- package/dist/types/platformos-liquid-docs.js +3 -0
- package/dist/types/platformos-liquid-docs.js.map +1 -0
- package/dist/types/schemas/index.d.ts +0 -2
- package/dist/types/schemas/index.js.map +1 -1
- package/dist/types.d.ts +26 -67
- package/dist/types.js +3 -5
- package/dist/types.js.map +1 -1
- package/dist/utils/block.js.map +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/yaml/parse.d.ts +5 -0
- package/dist/yaml/parse.js +94 -0
- package/dist/yaml/parse.js.map +1 -0
- package/package.json +4 -3
- package/src/{AugmentedThemeDocset.spec.ts → AugmentedPlatformOSDocset.spec.ts} +47 -34
- package/src/AugmentedPlatformOSDocset.ts +89 -0
- package/src/JSONValidator.ts +1 -1
- package/src/checks/deprecated-filter/index.spec.ts +76 -248
- package/src/checks/deprecated-filter/index.ts +5 -53
- package/src/checks/deprecated-tag/index.spec.ts +85 -34
- package/src/checks/deprecated-tag/index.ts +27 -22
- package/src/checks/duplicate-function-arguments/index.ts +1 -1
- package/src/checks/duplicate-render-partial-arguments/index.spec.ts +12 -12
- package/src/checks/duplicate-render-partial-arguments/index.ts +1 -1
- package/src/checks/graphql/index.ts +1 -1
- package/src/checks/graphql-variables/index.spec.ts +95 -0
- package/src/checks/graphql-variables/index.ts +4 -0
- package/src/checks/img-width-and-height/index.ts +2 -2
- package/src/checks/index.ts +11 -80
- package/src/checks/invalid-hash-assign-target/index.spec.ts +27 -27
- package/src/checks/json-syntax-error/index.ts +2 -2
- package/src/checks/liquid-html-syntax-error/checks/InvalidBooleanExpression.spec.ts +0 -11
- package/src/checks/liquid-html-syntax-error/checks/InvalidLoopArguments.spec.ts +1 -2
- package/src/checks/liquid-html-syntax-error/checks/InvalidLoopArguments.ts +2 -2
- package/src/checks/liquid-html-syntax-error/checks/InvalidTagSyntax.spec.ts +259 -0
- package/src/checks/liquid-html-syntax-error/checks/InvalidTagSyntax.ts +89 -0
- package/src/checks/liquid-html-syntax-error/checks/UnknownTag.spec.ts +293 -0
- package/src/checks/liquid-html-syntax-error/checks/UnknownTag.ts +43 -0
- package/src/checks/liquid-html-syntax-error/index.spec.ts +1 -6
- package/src/checks/liquid-html-syntax-error/index.ts +26 -5
- package/src/checks/matching-translations/index.spec.ts +187 -354
- package/src/checks/matching-translations/index.ts +117 -107
- package/src/checks/metadata-params/index.ts +6 -8
- package/src/checks/missing-asset/index.ts +1 -1
- package/src/checks/{missing-template → missing-partial}/index.spec.ts +6 -6
- package/src/checks/{missing-template → missing-partial}/index.ts +12 -26
- package/src/checks/orphaned-partial/index.ts +3 -3
- package/src/checks/parser-blocking-script/index.spec.ts +0 -118
- package/src/checks/parser-blocking-script/index.ts +3 -33
- package/src/checks/parser-blocking-script/suggestions.ts +1 -28
- package/src/checks/translation-key-exists/index.ts +1 -1
- package/src/checks/unclosed-html-element/index.ts +5 -1
- package/src/checks/undefined-object/index.spec.ts +32 -111
- package/src/checks/undefined-object/index.ts +15 -34
- package/src/checks/unique-doc-param-names/index.ts +1 -1
- package/src/checks/unknown-filter/index.spec.ts +2 -2
- package/src/checks/unknown-filter/index.ts +3 -3
- package/src/checks/unknown-property/index.ts +1 -1
- package/src/checks/unrecognized-render-partial-arguments/index.spec.ts +5 -5
- package/src/checks/unrecognized-render-partial-arguments/index.ts +2 -5
- package/src/checks/unused-assign/index.spec.ts +0 -30
- package/src/checks/unused-assign/index.ts +2 -2
- package/src/checks/unused-doc-param/index.ts +1 -1
- package/src/checks/utils.ts +1 -1
- package/src/checks/valid-doc-param-types/index.ts +4 -4
- package/src/checks/valid-html-translation/index.spec.ts +42 -32
- package/src/checks/valid-html-translation/index.ts +7 -7
- package/src/checks/valid-json/index.ts +2 -2
- package/src/checks/valid-render-partial-argument-types/index.spec.ts +13 -13
- package/src/checks/valid-render-partial-argument-types/index.ts +2 -5
- package/src/checks/variable-name/index.ts +1 -1
- package/src/context-utils.spec.ts +49 -77
- package/src/context-utils.ts +81 -129
- package/src/disabled-checks/index.spec.ts +26 -26
- package/src/disabled-checks/index.ts +2 -2
- package/src/disabled-checks/test-checks.ts +4 -4
- package/src/find-root.ts +12 -22
- package/src/fixes/autofix.spec.ts +2 -2
- package/src/fixes/autofix.ts +4 -4
- package/src/fixes/correctors/index.ts +4 -0
- package/src/ignore.spec.ts +4 -5
- package/src/index.ts +51 -21
- package/src/jsonc/parse.ts +1 -1
- package/src/liquid-doc/arguments.spec.ts +19 -45
- package/src/liquid-doc/arguments.ts +35 -42
- package/src/liquid-doc/liquidDoc.spec.ts +1 -1
- package/src/liquid-doc/liquidDoc.ts +1 -2
- package/src/liquid-doc/utils.ts +17 -8
- package/src/path.ts +16 -0
- package/src/test/MockApp.ts +17 -0
- package/src/test/MockFileSystem.spec.ts +10 -11
- package/src/test/MockFileSystem.ts +6 -6
- package/src/test/contain-offense.spec.ts +11 -3
- package/src/test/index.ts +1 -1
- package/src/test/test-helper.ts +43 -145
- package/src/to-source-code.ts +20 -1
- package/src/types/{theme-liquid-docs.ts → platformos-liquid-docs.ts} +8 -13
- package/src/types.ts +29 -92
- package/src/utils/index.ts +0 -1
- package/src/visitor.spec.ts +2 -2
- package/src/yaml/parse.ts +111 -0
- package/src/AugmentedThemeDocset.ts +0 -137
- package/src/checks/app-block-missing-schema/index.spec.ts +0 -121
- package/src/checks/app-block-missing-schema/index.ts +0 -46
- package/src/checks/app-block-valid-tags/index.spec.ts +0 -96
- package/src/checks/app-block-valid-tags/index.ts +0 -54
- package/src/checks/asset-preload/index.spec.ts +0 -78
- package/src/checks/asset-preload/index.ts +0 -65
- package/src/checks/asset-size-app-block-css/index.spec.ts +0 -88
- package/src/checks/asset-size-app-block-css/index.ts +0 -78
- package/src/checks/asset-size-app-block-javascript/index.spec.ts +0 -66
- package/src/checks/asset-size-app-block-javascript/index.ts +0 -78
- package/src/checks/asset-size-css/index.spec.ts +0 -166
- package/src/checks/asset-size-css/index.ts +0 -160
- package/src/checks/asset-size-javascript/index.spec.ts +0 -184
- package/src/checks/asset-size-javascript/index.ts +0 -144
- package/src/checks/block-id-usage/index.spec.ts +0 -76
- package/src/checks/block-id-usage/index.ts +0 -72
- package/src/checks/cdn-preconnect/index.spec.ts +0 -40
- package/src/checks/cdn-preconnect/index.ts +0 -43
- package/src/checks/content-for-header-modification/index.spec.ts +0 -65
- package/src/checks/content-for-header-modification/index.ts +0 -72
- package/src/checks/deprecate-bgsizes/index.spec.ts +0 -41
- package/src/checks/deprecate-bgsizes/index.ts +0 -49
- package/src/checks/deprecate-lazysizes/index.spec.ts +0 -26
- package/src/checks/deprecate-lazysizes/index.ts +0 -58
- package/src/checks/deprecated-filter/fixes.ts +0 -264
- package/src/checks/deprecated-fonts-on-sections-and-blocks/deprecated-fonts-data.ts +0 -1343
- package/src/checks/deprecated-fonts-on-sections-and-blocks/index.spec.ts +0 -613
- package/src/checks/deprecated-fonts-on-sections-and-blocks/index.ts +0 -284
- package/src/checks/deprecated-fonts-on-settings-schema/index.spec.ts +0 -102
- package/src/checks/deprecated-fonts-on-settings-schema/index.ts +0 -66
- package/src/checks/duplicate-content-for-arguments/index.spec.ts +0 -98
- package/src/checks/duplicate-content-for-arguments/index.ts +0 -43
- package/src/checks/empty-block-content/index.spec.ts +0 -117
- package/src/checks/empty-block-content/index.ts +0 -60
- package/src/checks/hardcoded-routes/index.spec.ts +0 -58
- package/src/checks/hardcoded-routes/index.ts +0 -100
- package/src/checks/json-missing-block/index.spec.ts +0 -435
- package/src/checks/json-missing-block/index.ts +0 -56
- package/src/checks/json-missing-block/missing-block-utils.ts +0 -147
- package/src/checks/liquid-free-settings/index.spec.ts +0 -180
- package/src/checks/liquid-free-settings/index.ts +0 -79
- package/src/checks/missing-content-for-arguments/index.spec.ts +0 -144
- package/src/checks/missing-content-for-arguments/index.ts +0 -46
- package/src/checks/pagination-size/index.spec.ts +0 -158
- package/src/checks/pagination-size/index.ts +0 -104
- package/src/checks/remote-asset/index.spec.ts +0 -280
- package/src/checks/remote-asset/index.ts +0 -238
- package/src/checks/reserved-doc-param-names/index.spec.ts +0 -62
- package/src/checks/reserved-doc-param-names/index.ts +0 -57
- package/src/checks/schema-presets-block-order/index.spec.ts +0 -344
- package/src/checks/schema-presets-block-order/index.ts +0 -154
- package/src/checks/schema-presets-static-blocks/index.spec.ts +0 -145
- package/src/checks/schema-presets-static-blocks/index.ts +0 -126
- package/src/checks/static-stylesheet-and-javascript-tags/index.spec.ts +0 -257
- package/src/checks/static-stylesheet-and-javascript-tags/index.ts +0 -48
- package/src/checks/unique-settings-id/index.spec.ts +0 -24
- package/src/checks/unique-settings-id/index.ts +0 -84
- package/src/checks/unique-settings-id/test-data.ts +0 -1191
- package/src/checks/unique-static-block-id/index.spec.ts +0 -55
- package/src/checks/unique-static-block-id/index.ts +0 -60
- package/src/checks/unrecognized-content-for-arguments/index.spec.ts +0 -145
- package/src/checks/unrecognized-content-for-arguments/index.ts +0 -55
- package/src/checks/valid-block-target/index.spec.ts +0 -1396
- package/src/checks/valid-block-target/index.ts +0 -142
- package/src/checks/valid-content-for-argument-types/index.spec.ts +0 -382
- package/src/checks/valid-content-for-argument-types/index.ts +0 -42
- package/src/checks/valid-content-for-arguments/index.spec.ts +0 -107
- package/src/checks/valid-content-for-arguments/index.ts +0 -98
- package/src/checks/valid-local-blocks/index.spec.ts +0 -286
- package/src/checks/valid-local-blocks/index.ts +0 -100
- package/src/checks/valid-local-blocks/valid-block-utils.ts +0 -97
- package/src/checks/valid-schema/index.spec.ts +0 -174
- package/src/checks/valid-schema/index.ts +0 -41
- package/src/checks/valid-schema-name/index.spec.ts +0 -112
- package/src/checks/valid-schema-name/index.ts +0 -75
- package/src/checks/valid-settings-key/index.spec.ts +0 -321
- package/src/checks/valid-settings-key/index.ts +0 -144
- package/src/checks/valid-static-block-type/index.spec.ts +0 -38
- package/src/checks/valid-static-block-type/index.ts +0 -58
- package/src/checks/valid-visible-if/index.spec.ts +0 -619
- package/src/checks/valid-visible-if/index.ts +0 -184
- package/src/checks/valid-visible-if/visible-if-utils.ts +0 -158
- package/src/tags/content-for.ts +0 -25
- package/src/to-schema.ts +0 -231
- package/src/types/schemas/index.ts +0 -5
- package/src/types/schemas/preset.ts +0 -52
- package/src/types/schemas/section.ts +0 -86
- package/src/types/schemas/setting.ts +0 -320
- package/src/types/schemas/template.ts +0 -34
- package/src/types/schemas/theme-block.ts +0 -34
- package/src/types/theme-schemas.ts +0 -80
- package/src/utils/block.ts +0 -300
- package/src/utils/markup.ts +0 -10
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { runLiquidCheck, highlightedOffenses } from '../../../test';
|
|
3
|
+
import { LiquidHTMLSyntaxError } from '../index';
|
|
4
|
+
|
|
5
|
+
describe('Module: InvalidTagSyntax', () => {
|
|
6
|
+
describe('render tag', () => {
|
|
7
|
+
it('should report render without quoted template name', async () => {
|
|
8
|
+
const sourceCode = `{% render %}`;
|
|
9
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
10
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
11
|
+
expect(syntaxOffenses).toHaveLength(1);
|
|
12
|
+
expect(syntaxOffenses[0].message).toContain("Invalid syntax for tag 'render'");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should not report valid render', async () => {
|
|
16
|
+
const sourceCode = `{% render 'partial' %}`;
|
|
17
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
18
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
19
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should not report valid render with arguments', async () => {
|
|
23
|
+
const sourceCode = `{% render 'partial', var1: 'hello', var2: 123 %}`;
|
|
24
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
25
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
26
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should highlight the entire invalid render tag', async () => {
|
|
30
|
+
const sourceCode = `Hello {% render %} world`;
|
|
31
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
32
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
33
|
+
const highlights = highlightedOffenses(sourceCode, syntaxOffenses);
|
|
34
|
+
expect(highlights).toContain('{% render %}');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('function tag', () => {
|
|
39
|
+
it('should report function without = operator', async () => {
|
|
40
|
+
const sourceCode = `{% function res 'path/to/function' %}`;
|
|
41
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
42
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
43
|
+
expect(syntaxOffenses).toHaveLength(1);
|
|
44
|
+
expect(syntaxOffenses[0].message).toContain("Invalid syntax for tag 'function'");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should not report valid function', async () => {
|
|
48
|
+
const sourceCode = `{% function res = 'path/to/function' %}`;
|
|
49
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
50
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
51
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should not report valid function with arguments', async () => {
|
|
55
|
+
const sourceCode = `{% function res = 'path/to/function', arg1: "hello" %}`;
|
|
56
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
57
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
58
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('graphql tag', () => {
|
|
63
|
+
it('should report invalid graphql syntax', async () => {
|
|
64
|
+
const sourceCode = `{% graphql %}`;
|
|
65
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
66
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
67
|
+
expect(syntaxOffenses).toHaveLength(1);
|
|
68
|
+
expect(syntaxOffenses[0].message).toContain("Invalid syntax for tag 'graphql'");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should not report valid graphql file-based syntax', async () => {
|
|
72
|
+
const sourceCode = `{% graphql result = 'path/to/query' %}`;
|
|
73
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
74
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
75
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should not report graphql with named argument value using a filter', async () => {
|
|
79
|
+
const sourceCode = `{% graphql consumers = 'modules/core/events/consumers', name: name | fetch: "admin_liquid_partials" %}`;
|
|
80
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
81
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
82
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should not report graphql with named argument value using chained filters', async () => {
|
|
86
|
+
const sourceCode = `{% graphql consumers = 'modules/core/events/consumers', name: name | fetch: "admin_liquid_partials" | fetch: "results" %}`;
|
|
87
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
88
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
89
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should not report graphql with multiple named arguments where one uses a filter', async () => {
|
|
93
|
+
const sourceCode = `{% graphql consumers = 'modules/core/events/consumers', name: name | fetch: "admin_liquid_partials" | fetch: "results", limit: 10 %}`;
|
|
94
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
95
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
96
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('include tag', () => {
|
|
101
|
+
it('should report include without template name', async () => {
|
|
102
|
+
const sourceCode = `{% include %}`;
|
|
103
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
104
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
105
|
+
expect(syntaxOffenses).toHaveLength(1);
|
|
106
|
+
expect(syntaxOffenses[0].message).toContain("Invalid syntax for tag 'include'");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should not report valid include', async () => {
|
|
110
|
+
const sourceCode = `{% include 'partial' %}`;
|
|
111
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
112
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
113
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('platformOS-specific tags', () => {
|
|
118
|
+
it('should not report valid log syntax', async () => {
|
|
119
|
+
const sourceCode = `{% log x %}`;
|
|
120
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
121
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
122
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should not report valid export syntax', async () => {
|
|
126
|
+
const sourceCode = `{% export data, namespace: "my_namespace" %}`;
|
|
127
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
128
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
129
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should not report valid redirect_to syntax', async () => {
|
|
133
|
+
const sourceCode = `{% redirect_to '/path' %}`;
|
|
134
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
135
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
136
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should not report valid print syntax', async () => {
|
|
140
|
+
const sourceCode = `{% print x %}`;
|
|
141
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
142
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
143
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should not report valid yield syntax', async () => {
|
|
147
|
+
const sourceCode = `{% yield 'content' %}`;
|
|
148
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
149
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
150
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('inside {% liquid %} blocks', () => {
|
|
155
|
+
it('should report invalid render syntax inside liquid block', async () => {
|
|
156
|
+
const sourceCode = `{% liquid
|
|
157
|
+
render
|
|
158
|
+
%}`;
|
|
159
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
160
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
161
|
+
expect(syntaxOffenses).toHaveLength(1);
|
|
162
|
+
expect(syntaxOffenses[0].message).toContain("Invalid syntax for tag 'render'");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should not report valid tags inside liquid block', async () => {
|
|
166
|
+
const sourceCode = `{% liquid
|
|
167
|
+
render 'partial'
|
|
168
|
+
function res = 'path/to/function'
|
|
169
|
+
%}`;
|
|
170
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
171
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
172
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('should NOT fire on tags with dedicated sub-checks', () => {
|
|
177
|
+
it('should not fire InvalidTagSyntax on assign (has MultipleAssignValues)', async () => {
|
|
178
|
+
const sourceCode = `{% assign x abc %}`;
|
|
179
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
180
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
181
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should not fire InvalidTagSyntax on echo (has InvalidEchoValue)', async () => {
|
|
185
|
+
const sourceCode = `{% echo = %}`;
|
|
186
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
187
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
188
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('should not report tags without expected markup', () => {
|
|
193
|
+
it('should not report else as invalid syntax', async () => {
|
|
194
|
+
const sourceCode = `{% if true %}a{% else %}b{% endif %}`;
|
|
195
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
196
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
197
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should not report break as invalid syntax', async () => {
|
|
201
|
+
const sourceCode = `{% for item in array %}{% break %}{% endfor %}`;
|
|
202
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
203
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
204
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should not report continue as invalid syntax', async () => {
|
|
208
|
+
const sourceCode = `{% for item in array %}{% continue %}{% endfor %}`;
|
|
209
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
210
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
211
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('tags with whitespace-trimming delimiters', () => {
|
|
216
|
+
it('should report invalid syntax with trimming delimiters', async () => {
|
|
217
|
+
const sourceCode = `{%- render -%}`;
|
|
218
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
219
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
220
|
+
expect(syntaxOffenses).toHaveLength(1);
|
|
221
|
+
expect(syntaxOffenses[0].message).toContain("Invalid syntax for tag 'render'");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should not report valid syntax with trimming delimiters', async () => {
|
|
225
|
+
const sourceCode = `{%- render 'partial' -%}`;
|
|
226
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
227
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
228
|
+
expect(syntaxOffenses).toHaveLength(0);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('docset syntax hint', () => {
|
|
233
|
+
it('should include syntax hint from docset when available', async () => {
|
|
234
|
+
const sourceCode = `{% render %}`;
|
|
235
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode, 'file.liquid', {
|
|
236
|
+
platformosDocset: {
|
|
237
|
+
async filters() {
|
|
238
|
+
return [];
|
|
239
|
+
},
|
|
240
|
+
async objects() {
|
|
241
|
+
return [];
|
|
242
|
+
},
|
|
243
|
+
async liquidDrops() {
|
|
244
|
+
return [];
|
|
245
|
+
},
|
|
246
|
+
async tags() {
|
|
247
|
+
return [{ name: 'render', syntax: "{% render 'partial' %}" }];
|
|
248
|
+
},
|
|
249
|
+
async graphQL() {
|
|
250
|
+
return null;
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
const syntaxOffenses = offenses.filter((o) => o.message.includes('Invalid syntax for tag'));
|
|
255
|
+
expect(syntaxOffenses).toHaveLength(1);
|
|
256
|
+
expect(syntaxOffenses[0].message).toContain("Expected syntax: {% render 'partial' %}");
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { LiquidTag, NamedTags, TAGS_WITHOUT_MARKUP } from '@platformos/liquid-html-parser';
|
|
2
|
+
import { Problem, SourceCodeType, TagEntry } from '../../..';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tags that use no markup at all — they are valid as `{% else %}`, `{% break %}`, etc.
|
|
6
|
+
* When their markup is a string, it's always '' (empty), so they should not trigger this check.
|
|
7
|
+
*/
|
|
8
|
+
const TAGS_WITH_NO_EXPECTED_MARKUP = new Set<string>(TAGS_WITHOUT_MARKUP);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tags that have dedicated sub-checks handling their string-markup cases with
|
|
12
|
+
* more specific error messages and autofixes. This check should NOT fire on these
|
|
13
|
+
* to avoid double-reporting or overriding their nuanced decisions.
|
|
14
|
+
*
|
|
15
|
+
* - assign → MultipleAssignValues, InvalidFilterName, InvalidPipeSyntax
|
|
16
|
+
* - echo → InvalidEchoValue, InvalidFilterName, InvalidPipeSyntax
|
|
17
|
+
* - if/elsif/unless → InvalidConditionalNode, InvalidConditionalNodeParenthesis
|
|
18
|
+
* - for/tablerow → InvalidLoopRange, InvalidLoopArguments
|
|
19
|
+
*/
|
|
20
|
+
const TAGS_WITH_DEDICATED_CHECKS = new Set<string>([
|
|
21
|
+
NamedTags.assign,
|
|
22
|
+
NamedTags.echo,
|
|
23
|
+
NamedTags.if,
|
|
24
|
+
NamedTags.elsif,
|
|
25
|
+
NamedTags.unless,
|
|
26
|
+
NamedTags.for,
|
|
27
|
+
NamedTags.tablerow,
|
|
28
|
+
NamedTags.when,
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* All tag names in the NamedTags enum — these have specific grammar rules for their markup.
|
|
33
|
+
* If a NamedTag's markup is a string (instead of a parsed object), it means the strict grammar
|
|
34
|
+
* rule failed and the tag fell through to the base case.
|
|
35
|
+
*/
|
|
36
|
+
const NAMED_TAGS = new Set<string>(Object.values(NamedTags));
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Detects known tags whose markup couldn't be parsed by the grammar.
|
|
40
|
+
*
|
|
41
|
+
* When the tolerant parser encounters a known tag name (e.g. "render") but can't parse
|
|
42
|
+
* the markup with the strict grammar rule, it falls back to the base case and stores
|
|
43
|
+
* the markup as a raw string. This function detects that situation.
|
|
44
|
+
*
|
|
45
|
+
* This check only applies to tags that DON'T have more specific sub-checks.
|
|
46
|
+
* Tags like assign, echo, if, for etc. have dedicated checks that provide
|
|
47
|
+
* better error messages and autofixes for their specific syntax patterns.
|
|
48
|
+
*
|
|
49
|
+
* Examples:
|
|
50
|
+
* {% graphql %} → name: 'graphql', markup: '' (string — invalid)
|
|
51
|
+
* {% render %} → name: 'render', markup: '' (string — invalid)
|
|
52
|
+
* {% function res 'path' %} → name: 'function', markup: "res 'path'" (string — invalid)
|
|
53
|
+
*/
|
|
54
|
+
export function detectInvalidTagSyntax(
|
|
55
|
+
node: LiquidTag,
|
|
56
|
+
tags: TagEntry[] = [],
|
|
57
|
+
): Problem<SourceCodeType.LiquidHtml> | undefined {
|
|
58
|
+
const tagName = node.name;
|
|
59
|
+
|
|
60
|
+
// Only check tags known to the grammar with specific markup rules
|
|
61
|
+
if (!NAMED_TAGS.has(tagName)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Tags without expected markup (else, break, continue, etc.) always have string markup
|
|
66
|
+
if (TAGS_WITH_NO_EXPECTED_MARKUP.has(tagName)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Skip tags that have dedicated sub-checks with more specific error handling
|
|
71
|
+
if (TAGS_WITH_DEDICATED_CHECKS.has(tagName)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// If markup is not a string, it was parsed successfully — no error
|
|
76
|
+
if (typeof node.markup !== 'string') {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Build a helpful hint from the docset if available
|
|
81
|
+
const tagEntry = tags.find((t) => t.name === tagName);
|
|
82
|
+
const syntaxHint = tagEntry?.syntax ? ` Expected syntax: ${tagEntry.syntax}` : '';
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
message: `Invalid syntax for tag '${tagName}'${syntaxHint}`,
|
|
86
|
+
startIndex: node.position.start,
|
|
87
|
+
endIndex: node.position.end,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { runLiquidCheck, highlightedOffenses } from '../../../test';
|
|
3
|
+
import { LiquidHTMLSyntaxError } from '../index';
|
|
4
|
+
|
|
5
|
+
describe('Module: UnknownTag', () => {
|
|
6
|
+
describe('standalone unknown tags', () => {
|
|
7
|
+
it('should report an unknown inline tag', async () => {
|
|
8
|
+
const sourceCode = `{% dsjkds %}`;
|
|
9
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
10
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
11
|
+
expect(unknownTagOffenses).toHaveLength(1);
|
|
12
|
+
expect(unknownTagOffenses[0].message).toBe("Unknown tag 'dsjkds'");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should report an unknown tag with markup', async () => {
|
|
16
|
+
const sourceCode = `{% foobar some_arg %}`;
|
|
17
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
18
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
19
|
+
expect(unknownTagOffenses).toHaveLength(1);
|
|
20
|
+
expect(unknownTagOffenses[0].message).toBe("Unknown tag 'foobar'");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should highlight the entire unknown tag', async () => {
|
|
24
|
+
const sourceCode = `Hello {% unknown_tag %} world`;
|
|
25
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
26
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
27
|
+
const highlights = highlightedOffenses(sourceCode, unknownTagOffenses);
|
|
28
|
+
expect(highlights).toContain('{% unknown_tag %}');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should report multiple unknown tags', async () => {
|
|
32
|
+
const sourceCode = `{% foo %} {% bar %} {% baz %}`;
|
|
33
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
34
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
35
|
+
expect(unknownTagOffenses).toHaveLength(3);
|
|
36
|
+
expect(unknownTagOffenses[0].message).toBe("Unknown tag 'foo'");
|
|
37
|
+
expect(unknownTagOffenses[1].message).toBe("Unknown tag 'bar'");
|
|
38
|
+
expect(unknownTagOffenses[2].message).toBe("Unknown tag 'baz'");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('unknown tags inside {% liquid %} blocks', () => {
|
|
43
|
+
it('should report an unknown tag inside a liquid block', async () => {
|
|
44
|
+
const sourceCode = `{% liquid
|
|
45
|
+
assign x = "abc"
|
|
46
|
+
dasjkdjkas
|
|
47
|
+
%}`;
|
|
48
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
49
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
50
|
+
expect(unknownTagOffenses).toHaveLength(1);
|
|
51
|
+
expect(unknownTagOffenses[0].message).toBe("Unknown tag 'dasjkdjkas'");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should report multiple unknown tags inside a liquid block', async () => {
|
|
55
|
+
const sourceCode = `{% liquid
|
|
56
|
+
assign x = "abc"
|
|
57
|
+
foo
|
|
58
|
+
echo x
|
|
59
|
+
bar
|
|
60
|
+
%}`;
|
|
61
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
62
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
63
|
+
expect(unknownTagOffenses).toHaveLength(2);
|
|
64
|
+
expect(unknownTagOffenses[0].message).toBe("Unknown tag 'foo'");
|
|
65
|
+
expect(unknownTagOffenses[1].message).toBe("Unknown tag 'bar'");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should not report valid tags inside a liquid block', async () => {
|
|
69
|
+
const sourceCode = `{% liquid
|
|
70
|
+
assign x = "hello"
|
|
71
|
+
echo x
|
|
72
|
+
assign y = x | upcase
|
|
73
|
+
%}`;
|
|
74
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
75
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
76
|
+
expect(unknownTagOffenses).toHaveLength(0);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('should NOT report known tags', () => {
|
|
81
|
+
it('should not report standard liquid tags', async () => {
|
|
82
|
+
const validTags = [
|
|
83
|
+
`{% assign x = "hello" %}`,
|
|
84
|
+
`{% echo "hello" %}`,
|
|
85
|
+
`{% increment counter %}`,
|
|
86
|
+
`{% decrement counter %}`,
|
|
87
|
+
`{% cycle "a", "b", "c" %}`,
|
|
88
|
+
`{% break %}`,
|
|
89
|
+
`{% continue %}`,
|
|
90
|
+
`{% layout 'application' %}`,
|
|
91
|
+
`{% render 'partial' %}`,
|
|
92
|
+
`{% include 'partial' %}`,
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
for (const sourceCode of validTags) {
|
|
96
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
97
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
98
|
+
expect(
|
|
99
|
+
unknownTagOffenses,
|
|
100
|
+
`Expected no unknown tag offense for: ${sourceCode}`,
|
|
101
|
+
).toHaveLength(0);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should not report block tags', async () => {
|
|
106
|
+
const validBlocks = [
|
|
107
|
+
`{% if true %}hello{% endif %}`,
|
|
108
|
+
`{% unless false %}hello{% endunless %}`,
|
|
109
|
+
`{% for item in array %}{{ item }}{% endfor %}`,
|
|
110
|
+
`{% capture var %}hello{% endcapture %}`,
|
|
111
|
+
`{% case x %}{% when 1 %}one{% endcase %}`,
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
for (const sourceCode of validBlocks) {
|
|
115
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
116
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
117
|
+
expect(
|
|
118
|
+
unknownTagOffenses,
|
|
119
|
+
`Expected no unknown tag offense for: ${sourceCode}`,
|
|
120
|
+
).toHaveLength(0);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should not report platformOS-specific tags', async () => {
|
|
125
|
+
const validTags = [
|
|
126
|
+
`{% log x %}`,
|
|
127
|
+
`{% print x %}`,
|
|
128
|
+
`{% yield 'content' %}`,
|
|
129
|
+
`{% redirect_to '/path' %}`,
|
|
130
|
+
`{% export x, namespace: "ns" %}`,
|
|
131
|
+
`{% return x %}`,
|
|
132
|
+
`{% response_status 200 %}`,
|
|
133
|
+
`{% response_headers 'Content-Type': 'text/html' %}`,
|
|
134
|
+
`{% sign_in user %}`,
|
|
135
|
+
`{% spam_protection "recaptcha_v2" %}`,
|
|
136
|
+
`{% theme_render_rc 'rc' %}`,
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
for (const sourceCode of validTags) {
|
|
140
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
141
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
142
|
+
expect(
|
|
143
|
+
unknownTagOffenses,
|
|
144
|
+
`Expected no unknown tag offense for: ${sourceCode}`,
|
|
145
|
+
).toHaveLength(0);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should not report platformOS block tags', async () => {
|
|
150
|
+
const validBlocks = [
|
|
151
|
+
`{% cache 'key' %}hello{% endcache %}`,
|
|
152
|
+
`{% parse_json var %}{}{% endparse_json %}`,
|
|
153
|
+
`{% try %}hello{% catch err %}{{ err }}{% endtry %}`,
|
|
154
|
+
`{% content_for 'pagetitle' %}<title>Hello</title>{% endcontent_for %}`,
|
|
155
|
+
`{% background source_name: 'my_task' %}echo "hello"{% endbackground %}`,
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
for (const sourceCode of validBlocks) {
|
|
159
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
160
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
161
|
+
expect(
|
|
162
|
+
unknownTagOffenses,
|
|
163
|
+
`Expected no unknown tag offense for: ${sourceCode}`,
|
|
164
|
+
).toHaveLength(0);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should not report raw tags', async () => {
|
|
169
|
+
const sourceCode = `{% raw %}{{ not liquid }}{% endraw %}`;
|
|
170
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
171
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
172
|
+
expect(unknownTagOffenses).toHaveLength(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should not report comment tags', async () => {
|
|
176
|
+
const sourceCode = `{% comment %}this is a comment{% endcomment %}`;
|
|
177
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
178
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
179
|
+
expect(unknownTagOffenses).toHaveLength(0);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should not report inline comment tags', async () => {
|
|
183
|
+
const sourceCode = `{% # this is an inline comment %}`;
|
|
184
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
185
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
186
|
+
expect(unknownTagOffenses).toHaveLength(0);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should not report else/elsif tags', async () => {
|
|
190
|
+
const sourceCode = `{% if true %}a{% elsif false %}b{% else %}c{% endif %}`;
|
|
191
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
192
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
193
|
+
expect(unknownTagOffenses).toHaveLength(0);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('tags known via docset', () => {
|
|
198
|
+
it('should not report tags from the docset', async () => {
|
|
199
|
+
const sourceCode = `{% custom_docset_tag %}`;
|
|
200
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode, 'file.liquid', {
|
|
201
|
+
platformosDocset: {
|
|
202
|
+
async filters() {
|
|
203
|
+
return [];
|
|
204
|
+
},
|
|
205
|
+
async objects() {
|
|
206
|
+
return [];
|
|
207
|
+
},
|
|
208
|
+
async liquidDrops() {
|
|
209
|
+
return [];
|
|
210
|
+
},
|
|
211
|
+
async tags() {
|
|
212
|
+
return [{ name: 'custom_docset_tag' }];
|
|
213
|
+
},
|
|
214
|
+
async graphQL() {
|
|
215
|
+
return null;
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
220
|
+
expect(unknownTagOffenses).toHaveLength(0);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('mixed valid and unknown tags', () => {
|
|
225
|
+
it('should only report the unknown tags in mixed content', async () => {
|
|
226
|
+
const sourceCode = `
|
|
227
|
+
{% assign x = "hello" %}
|
|
228
|
+
{% unknown_one %}
|
|
229
|
+
{% if true %}
|
|
230
|
+
{% bogus_tag %}
|
|
231
|
+
{% endif %}
|
|
232
|
+
`;
|
|
233
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
234
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
235
|
+
expect(unknownTagOffenses).toHaveLength(2);
|
|
236
|
+
expect(unknownTagOffenses[0].message).toBe("Unknown tag 'unknown_one'");
|
|
237
|
+
expect(unknownTagOffenses[1].message).toBe("Unknown tag 'bogus_tag'");
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('real-world file patterns', () => {
|
|
242
|
+
it('should catch unknown tags in a real platformOS page with liquid block and standalone tag', async () => {
|
|
243
|
+
const sourceCode = `---
|
|
244
|
+
method: post
|
|
245
|
+
slug: users
|
|
246
|
+
layout: 'modules/community/blank'
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
{% liquid
|
|
250
|
+
function current_profile = 'modules/user/helpers/current_profile'
|
|
251
|
+
|
|
252
|
+
include 'modules/user/helpers/can_do_or_redirect', requester: current_profile, do: 'users.register', redirect_url: "/"
|
|
253
|
+
|
|
254
|
+
function object = 'modules/user/commands/user/create', first_name: params.first_name
|
|
255
|
+
|
|
256
|
+
dsk
|
|
257
|
+
|
|
258
|
+
if object.valid
|
|
259
|
+
function _ = 'modules/user/commands/session/create', user_id: object.id
|
|
260
|
+
include 'modules/core/helpers/redirect_to', url: '/onboarding'
|
|
261
|
+
else
|
|
262
|
+
assign values = object | default: null
|
|
263
|
+
render 'modules/user/users/new', errors: object.errors, values: values
|
|
264
|
+
endif
|
|
265
|
+
%}
|
|
266
|
+
|
|
267
|
+
{% jakdsajk %}`;
|
|
268
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
269
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
270
|
+
expect(unknownTagOffenses).toHaveLength(2);
|
|
271
|
+
expect(unknownTagOffenses[0].message).toBe("Unknown tag 'dsk'");
|
|
272
|
+
expect(unknownTagOffenses[1].message).toBe("Unknown tag 'jakdsajk'");
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('edge cases', () => {
|
|
277
|
+
it('should report unknown tags with underscores', async () => {
|
|
278
|
+
const sourceCode = `{% my_custom_tag %}`;
|
|
279
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
280
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
281
|
+
expect(unknownTagOffenses).toHaveLength(1);
|
|
282
|
+
expect(unknownTagOffenses[0].message).toBe("Unknown tag 'my_custom_tag'");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should report unknown tags with whitespace-trimming delimiters', async () => {
|
|
286
|
+
const sourceCode = `{%- unknown_tag -%}`;
|
|
287
|
+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
|
|
288
|
+
const unknownTagOffenses = offenses.filter((o) => o.message.includes('Unknown tag'));
|
|
289
|
+
expect(unknownTagOffenses).toHaveLength(1);
|
|
290
|
+
expect(unknownTagOffenses[0].message).toBe("Unknown tag 'unknown_tag'");
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LiquidTag,
|
|
3
|
+
NamedTags,
|
|
4
|
+
TAGS_WITHOUT_MARKUP,
|
|
5
|
+
BLOCKS,
|
|
6
|
+
RAW_TAGS,
|
|
7
|
+
} from '@platformos/liquid-html-parser';
|
|
8
|
+
import { Problem, SourceCodeType, TagEntry } from '../../..';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* All tag names known to the grammar (NamedTags enum + TAGS_WITHOUT_MARKUP + BLOCKS + RAW_TAGS).
|
|
12
|
+
* These are tags that the parser recognizes with specific grammar rules.
|
|
13
|
+
*/
|
|
14
|
+
const GRAMMAR_KNOWN_TAGS = new Set<string>([
|
|
15
|
+
...Object.values(NamedTags),
|
|
16
|
+
...TAGS_WITHOUT_MARKUP,
|
|
17
|
+
...BLOCKS,
|
|
18
|
+
...RAW_TAGS,
|
|
19
|
+
'#', // inline comment: {% # this is a comment %}
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export function detectUnknownTag(
|
|
23
|
+
node: LiquidTag,
|
|
24
|
+
tags: TagEntry[] = [],
|
|
25
|
+
): Problem<SourceCodeType.LiquidHtml> | undefined {
|
|
26
|
+
const tagName = node.name;
|
|
27
|
+
|
|
28
|
+
// If the tag is known to the grammar, it's not unknown
|
|
29
|
+
if (GRAMMAR_KNOWN_TAGS.has(tagName)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// If the tag is known to the docset, it's not unknown
|
|
34
|
+
if (tags.some((tag) => tag.name === tagName)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
message: `Unknown tag '${tagName}'`,
|
|
40
|
+
startIndex: node.position.start,
|
|
41
|
+
endIndex: node.position.end,
|
|
42
|
+
};
|
|
43
|
+
}
|