Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@shopify/theme-check-common

Package Overview
Dependencies
Maintainers
25
Versions
40
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@shopify/theme-check-common - npm Package Compare versions

Comparing version 1.16.0 to 1.16.1

dist/AugmentedSchemaValidators.d.ts

10

CHANGELOG.md
# @shopify/theme-check-common
## 1.16.1
### Patch Changes
- 8d35241: Fix `paginate` object completion, hover and `UndefinedObject` reporting
- 201f30c: Add support for `{% layout none %}`
- c0298e7: Fix `recommendations` completion, hover and `UndefinedObject` reporting
- 6fad756: Fix `predictive_search` completion, hover and `UndefinedObject` reporting
- fc86c91: Fix `form` object completion, hover and `UndefinedObject` reporting
## 1.16.0

@@ -4,0 +14,0 @@

49

dist/checks/undefined-object/index.js

@@ -26,3 +26,4 @@ "use strict";

*/
if (context.relativePath(context.file.absolutePath).startsWith('snippets/')) {
const relativePath = context.relativePath(context.file.absolutePath);
if (relativePath.startsWith('snippets/')) {
return {};

@@ -48,3 +49,3 @@ }

async LiquidTag(node) {
var _a, _b, _c;
var _a, _b, _c, _d;
if (isLiquidTagAssign(node)) {

@@ -60,10 +61,34 @@ indexVariableScope(node.markup.name, {

}
/**
* {% form 'cart', cart %}
* {{ form }}
* {% endform %}
*/
if (['form', 'paginate'].includes(node.name)) {
indexVariableScope(node.name, {
start: node.blockStartPosition.end,
end: (_b = node.blockEndPosition) === null || _b === void 0 ? void 0 : _b.start,
});
}
/* {% layout none %} */
if (node.name === 'layout') {
indexVariableScope('none', {
start: node.position.start,
end: node.position.end,
});
}
/**
* {% for x in y %}
* {{ forloop }}
* {{ x }}
* {% endfor %}
*/
if (isLiquidForTag(node) || isLiquidTableRowTag(node)) {
indexVariableScope(node.markup.variableName, {
start: node.blockStartPosition.end,
end: (_b = node.blockEndPosition) === null || _b === void 0 ? void 0 : _b.start,
end: (_c = node.blockEndPosition) === null || _c === void 0 ? void 0 : _c.start,
});
indexVariableScope(node.name === 'for' ? 'forloop' : 'tablerowloop', {
start: node.blockStartPosition.end,
end: (_c = node.blockEndPosition) === null || _c === void 0 ? void 0 : _c.start,
end: (_d = node.blockEndPosition) === null || _d === void 0 ? void 0 : _d.start,
});

@@ -79,3 +104,3 @@ }

async onCodePathEnd() {
const objects = await globalObjects(themeDocset);
const objects = await globalObjects(themeDocset, relativePath);
objects.forEach((obj) => variableScopes.set(obj.name, []));

@@ -98,9 +123,19 @@ variables.forEach((variable) => {

};
async function globalObjects(themeDocset) {
async function globalObjects(themeDocset, relativePath) {
const objects = await themeDocset.objects();
const contextualObjects = getContextualObjects(relativePath);
const globalObjects = objects.filter(({ access, name }) => {
return name === 'section' || !access || access.global === true || access.template.length > 0;
return (contextualObjects.includes(name) ||
!access ||
access.global === true ||
access.template.length > 0);
});
return globalObjects;
}
function getContextualObjects(relativePath) {
if (relativePath.startsWith('sections/')) {
return ['section', 'predictive_search', 'recommendations'];
}
return [];
}
function isDefined(variableName, variablePosition, scopedVariables) {

@@ -107,0 +142,0 @@ const scopes = scopedVariables.get(variableName);

@@ -153,2 +153,32 @@ "use strict";

});
(0, vitest_1.it)('should contextually report on the undefined nature of the paginate object (defined in paginate tag, undefined outside)', async () => {
const sourceCode = `
{% assign col = 'string' | split: '' %}
{% paginate col by 5 %}
{{ paginate }}
{% endpaginate %}{{ paginate }}
`;
const offenses = await (0, test_1.runLiquidCheck)(index_1.UndefinedObject, sourceCode);
(0, vitest_1.expect)(offenses).toHaveLength(1);
(0, vitest_1.expect)(offenses.map((e) => e.message)).toEqual(["Unknown object 'paginate' used."]);
});
(0, vitest_1.it)('should contextually report on the undefined nature of the form object (defined in form tag, undefined outside)', async () => {
const sourceCode = `
{% form "cart" %}
{{ form }}
{% endform %}{{ form }}
`;
const offenses = await (0, test_1.runLiquidCheck)(index_1.UndefinedObject, sourceCode);
(0, vitest_1.expect)(offenses).toHaveLength(1);
(0, vitest_1.expect)(offenses.map((e) => e.message)).toEqual(["Unknown object 'form' used."]);
});
(0, vitest_1.it)('should support {% layout none %}', async () => {
const sourceCode = `
{% layout none %}
{{ none }}
`;
const offenses = await (0, test_1.runLiquidCheck)(index_1.UndefinedObject, sourceCode);
(0, vitest_1.expect)(offenses).toHaveLength(1);
(0, vitest_1.expect)(offenses.map((e) => e.message)).toEqual(["Unknown object 'none' used."]);
});
(0, vitest_1.it)('should not report an offense when object is undefined in a "snippet" file', async () => {

@@ -180,2 +210,16 @@ const sourceCode = `

});
(0, vitest_1.it)('should support contextual exceptions', async () => {
let offenses;
const contexts = [
['section', 'sections/section.liquid'],
['predictive_search', 'sections/predictive-search.liquid'],
['recommendations', 'sections/recommendations.liquid'],
];
for (const [object, goodPath] of contexts) {
offenses = await (0, test_1.runLiquidCheck)(index_1.UndefinedObject, `{{ ${object} }}`, goodPath);
(0, vitest_1.expect)(offenses).toHaveLength(0);
offenses = await (0, test_1.runLiquidCheck)(index_1.UndefinedObject, `{{ ${object} }}`, 'file.liquid');
(0, vitest_1.expect)(offenses).toHaveLength(1);
}
});
(0, vitest_1.it)('should report an offense for forloop/tablerowloop used outside of context', async () => {

@@ -182,0 +226,0 @@ const sourceCode = `

import { Config, Dependencies, Offense, Theme } from './types';
export * from './AugmentedThemeDocset';
export * from './AugmentedSchemaValidators';
export * from './fixes';

@@ -7,2 +9,4 @@ export * from './types';

export * from './ignore';
export * from './utils/types';
export * from './utils/memo';
export declare function check(sourceCodes: Theme, config: Config, dependencies: Dependencies): Promise<Offense[]>;

@@ -36,2 +36,6 @@ "use strict";

const ignore_1 = require("./ignore");
const AugmentedThemeDocset_1 = require("./AugmentedThemeDocset");
const AugmentedSchemaValidators_1 = require("./AugmentedSchemaValidators");
__exportStar(require("./AugmentedThemeDocset"), exports);
__exportStar(require("./AugmentedSchemaValidators"), exports);
__exportStar(require("./fixes"), exports);

@@ -42,2 +46,4 @@ __exportStar(require("./types"), exports);

__exportStar(require("./ignore"), exports);
__exportStar(require("./utils/types"), exports);
__exportStar(require("./utils/memo"), exports);
const defaultErrorHandler = (_error) => {

@@ -50,2 +56,9 @@ // Silently ignores errors by default.

const { DisabledChecksVisitor, isDisabled } = (0, disabled_checks_1.createDisabledChecksModule)();
// We're memozing those deps here because they shouldn't change within a run.
if (dependencies.themeDocset && !dependencies.themeDocset.isAugmented) {
dependencies.themeDocset = new AugmentedThemeDocset_1.AugmentedThemeDocset(dependencies.themeDocset);
}
if (dependencies.schemaValidators && !dependencies.schemaValidators.isAugmented) {
dependencies.schemaValidators = new AugmentedSchemaValidators_1.AugmentedSchemaValidators(dependencies.schemaValidators);
}
for (const type of Object.values(types_1.SourceCodeType)) {

@@ -52,0 +65,0 @@ switch (type) {

@@ -110,2 +110,26 @@ "use strict";

},
{
name: 'section',
access: {
global: false,
parents: [],
template: [],
},
},
{
name: 'predictive_search',
access: {
global: false,
parents: [],
template: [],
},
},
{
name: 'recommendations',
access: {
global: false,
parents: [],
template: [],
},
},
];

@@ -112,0 +136,0 @@ },

2

dist/types.d.ts

@@ -333,3 +333,1 @@ import { NodeTypes as LiquidHtmlNodeTypes, LiquidHtmlNode } from '@shopify/liquid-html-parser';

}
export type WithRequired<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;
export type WithOptional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;

@@ -5,13 +5,9 @@ /**

export interface ThemeDocset {
/**
* Returns Liquid filters available on themes.
*/
/** Whether it was augmented prior to being passed. */
isAugmented?: boolean;
/** Returns Liquid filters available on themes. */
filters(): Promise<FilterEntry[]>;
/**
* Returns objects (or Liquid variables) available on themes.
*/
/** Returns objects (or Liquid variables) available on themes. */
objects(): Promise<ObjectEntry[]>;
/**
* Returns Liquid tags available on themes.
*/
/** Returns Liquid tags available on themes. */
tags(): Promise<TagEntry[]>;

@@ -23,5 +19,5 @@ }

export interface JsonSchemaValidators {
/**
* Retrieves the JSON schema validator for theme sections.
*/
/** Whether it was augmented prior to being passed. */
isAugmented?: boolean;
/** Retrieves the JSON schema validator for theme sections. */
validateSectionSchema(): Promise<ValidateFunction>;

@@ -28,0 +24,0 @@ }

export * from './array';
export * from './position';
export * from './error';
export * from './types';
export * from './memo';

@@ -20,2 +20,4 @@ "use strict";

__exportStar(require("./error"), exports);
__exportStar(require("./types"), exports);
__exportStar(require("./memo"), exports);
//# sourceMappingURL=index.js.map
{
"name": "@shopify/theme-check-common",
"version": "1.16.0",
"version": "1.16.1",
"license": "MIT",

@@ -5,0 +5,0 @@ "homepage": "https://github.com/Shopify/theme-tools/blob/main/packages/theme-check-common/README.md",

import { expect, describe, it } from 'vitest';
import { UndefinedObject } from './index';
import { runLiquidCheck, highlightedOffenses } from '../../test';
import { Offense } from '../../types';

@@ -196,2 +197,41 @@ describe('Module: UndefinedObject', () => {

it('should contextually report on the undefined nature of the paginate object (defined in paginate tag, undefined outside)', async () => {
const sourceCode = `
{% assign col = 'string' | split: '' %}
{% paginate col by 5 %}
{{ paginate }}
{% endpaginate %}{{ paginate }}
`;
const offenses = await runLiquidCheck(UndefinedObject, sourceCode);
expect(offenses).toHaveLength(1);
expect(offenses.map((e) => e.message)).toEqual(["Unknown object 'paginate' used."]);
});
it('should contextually report on the undefined nature of the form object (defined in form tag, undefined outside)', async () => {
const sourceCode = `
{% form "cart" %}
{{ form }}
{% endform %}{{ form }}
`;
const offenses = await runLiquidCheck(UndefinedObject, sourceCode);
expect(offenses).toHaveLength(1);
expect(offenses.map((e) => e.message)).toEqual(["Unknown object 'form' used."]);
});
it('should support {% layout none %}', async () => {
const sourceCode = `
{% layout none %}
{{ none }}
`;
const offenses = await runLiquidCheck(UndefinedObject, sourceCode);
expect(offenses).toHaveLength(1);
expect(offenses.map((e) => e.message)).toEqual(["Unknown object 'none' used."]);
});
it('should not report an offense when object is undefined in a "snippet" file', async () => {

@@ -232,2 +272,17 @@ const sourceCode = `

it('should support contextual exceptions', async () => {
let offenses: Offense[];
const contexts: [string, string][] = [
['section', 'sections/section.liquid'],
['predictive_search', 'sections/predictive-search.liquid'],
['recommendations', 'sections/recommendations.liquid'],
];
for (const [object, goodPath] of contexts) {
offenses = await runLiquidCheck(UndefinedObject, `{{ ${object} }}`, goodPath);
expect(offenses).toHaveLength(0);
offenses = await runLiquidCheck(UndefinedObject, `{{ ${object} }}`, 'file.liquid');
expect(offenses).toHaveLength(1);
}
});
it('should report an offense for forloop/tablerowloop used outside of context', async () => {

@@ -234,0 +289,0 @@ const sourceCode = `

@@ -38,3 +38,4 @@ import {

*/
if (context.relativePath(context.file.absolutePath).startsWith('snippets/')) {
const relativePath = context.relativePath(context.file.absolutePath);
if (relativePath.startsWith('snippets/')) {
return {};

@@ -75,2 +76,28 @@ }

/**
* {% form 'cart', cart %}
* {{ form }}
* {% endform %}
*/
if (['form', 'paginate'].includes(node.name)) {
indexVariableScope(node.name, {
start: node.blockStartPosition.end,
end: node.blockEndPosition?.start,
});
}
/* {% layout none %} */
if (node.name === 'layout') {
indexVariableScope('none', {
start: node.position.start,
end: node.position.end,
});
}
/**
* {% for x in y %}
* {{ forloop }}
* {{ x }}
* {% endfor %}
*/
if (isLiquidForTag(node) || isLiquidTableRowTag(node)) {

@@ -97,3 +124,3 @@ indexVariableScope(node.markup.variableName, {

async onCodePathEnd() {
const objects = await globalObjects(themeDocset);
const objects = await globalObjects(themeDocset, relativePath);

@@ -119,7 +146,13 @@ objects.forEach((obj) => variableScopes.set(obj.name, []));

async function globalObjects(themeDocset: ThemeDocset) {
async function globalObjects(themeDocset: ThemeDocset, relativePath: string) {
const objects = await themeDocset.objects();
const contextualObjects = getContextualObjects(relativePath);
const globalObjects = objects.filter(({ access, name }) => {
return name === 'section' || !access || access.global === true || access.template.length > 0;
return (
contextualObjects.includes(name) ||
!access ||
access.global === true ||
access.template.length > 0
);
});

@@ -130,2 +163,10 @@

function getContextualObjects(relativePath: string): string[] {
if (relativePath.startsWith('sections/')) {
return ['section', 'predictive_search', 'recommendations'];
}
return [];
}
function isDefined(

@@ -132,0 +173,0 @@ variableName: string,

@@ -1,2 +0,3 @@

import { FixApplicator, Offense, SourceCodeType, Theme, WithRequired } from '../types';
import { FixApplicator, Offense, SourceCodeType, Theme } from '../types';
import { WithRequired } from '../utils/types';
import { createCorrector } from './correctors';

@@ -3,0 +4,0 @@ import { flattenFixes } from './utils';

@@ -27,3 +27,7 @@ import {

import { isIgnored } from './ignore';
import { AugmentedThemeDocset } from './AugmentedThemeDocset';
import { AugmentedSchemaValidators } from './AugmentedSchemaValidators';
export * from './AugmentedThemeDocset';
export * from './AugmentedSchemaValidators';
export * from './fixes';

@@ -34,2 +38,4 @@ export * from './types';

export * from './ignore';
export * from './utils/types';
export * from './utils/memo';

@@ -49,2 +55,11 @@ const defaultErrorHandler = (_error: Error): void => {

// We're memozing those deps here because they shouldn't change within a run.
if (dependencies.themeDocset && !dependencies.themeDocset.isAugmented) {
dependencies.themeDocset = new AugmentedThemeDocset(dependencies.themeDocset);
}
if (dependencies.schemaValidators && !dependencies.schemaValidators.isAugmented) {
dependencies.schemaValidators = new AugmentedSchemaValidators(dependencies.schemaValidators);
}
for (const type of Object.values(SourceCodeType)) {

@@ -51,0 +66,0 @@ switch (type) {

@@ -144,2 +144,26 @@ import {

},
{
name: 'section',
access: {
global: false,
parents: [],
template: [],
},
},
{
name: 'predictive_search',
access: {
global: false,
parents: [],
template: [],
},
},
{
name: 'recommendations',
access: {
global: false,
parents: [],
template: [],
},
},
];

@@ -146,0 +170,0 @@ },

@@ -434,4 +434,1 @@ import { NodeTypes as LiquidHtmlNodeTypes, LiquidHtmlNode } from '@shopify/liquid-html-parser';

}
export type WithRequired<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;
export type WithOptional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;

@@ -5,15 +5,12 @@ /**

export interface ThemeDocset {
/**
* Returns Liquid filters available on themes.
*/
/** Whether it was augmented prior to being passed. */
isAugmented?: boolean;
/** Returns Liquid filters available on themes. */
filters(): Promise<FilterEntry[]>;
/**
* Returns objects (or Liquid variables) available on themes.
*/
/** Returns objects (or Liquid variables) available on themes. */
objects(): Promise<ObjectEntry[]>;
/**
* Returns Liquid tags available on themes.
*/
/** Returns Liquid tags available on themes. */
tags(): Promise<TagEntry[]>;

@@ -26,5 +23,6 @@ }

export interface JsonSchemaValidators {
/**
* Retrieves the JSON schema validator for theme sections.
*/
/** Whether it was augmented prior to being passed. */
isAugmented?: boolean;
/** Retrieves the JSON schema validator for theme sections. */
validateSectionSchema(): Promise<ValidateFunction>;

@@ -31,0 +29,0 @@ }

export * from './array';
export * from './position';
export * from './error';
export * from './types';
export * from './memo';

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc