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
24
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 2.9.2 to 3.0.0

dist/AbstractFileSystem.d.ts

36

CHANGELOG.md
# @shopify/theme-check-common
## 3.0.0
### Major Changes
- 4b574c1: [Breaking] Replace absolute path concerns with URIs
This implies a couple of changes:
- `Config` now holds a `rootUri` instead of `root` path.
- `loadConfig` injections needs to change their return value accordingly
- In checks,
- The context helper `absolutePath` has been replaced by `toUri`
- The context helper `relativePath` has been replaced by `toRelativePath`
- `SourceCode` objects now hold a `uri` instead of a `path`
- `toSourceCode` now accepts a `uri` instead of a `path`
- 4b574c1: [Breaking] Replace fs-based dependency injections with AbstractFileSystem injection
```diff
runChecks(theme, {
- getDefaultTranslations,
- getDefaultLocale,
- getDefaultSchemaLocale,
- getDefaultSchemaTranslations,
- fileExists,
- fileSize,
+ fs: new FileSystemImpl(),
themeDocset,
jsonValidationSet,
})
```
### Patch Changes
- 5fab0e9: (Internal) Add path.basename util
## 2.9.2

@@ -4,0 +40,0 @@

24

dist/checks/index.d.ts

@@ -36,6 +36,6 @@ import { ConfigTarget, JSONCheckDefinition, LiquidCheckDefinition } from '../types';

}): void;
relativePath(absolutePath: string): string;
absolutePath(relativePath: string): string;
toRelativePath(uri: string): string;
toUri(relativePath: string): string;
file: {
absolutePath: string;
uri: string;
type: import("../types").SourceCodeType.LiquidHtml;

@@ -47,3 +47,3 @@ version?: number | undefined;

validateJSON?: import("../types").ValidateJSON<import("../types").SourceCodeType.LiquidHtml> | undefined;
} & import("../types").Dependencies & {
} & import("../types").AugmentedDependencies & {
settings: import("../types").Settings<import("../types").Schema>;

@@ -124,3 +124,3 @@ }): Partial<Partial<{

onCodePathStart(file: {
absolutePath: string;
uri: string;
type: import("../types").SourceCodeType.LiquidHtml;

@@ -132,3 +132,3 @@ version?: number | undefined;

onCodePathEnd(file: {
absolutePath: string;
uri: string;
type: import("../types").SourceCodeType.LiquidHtml;

@@ -170,6 +170,6 @@ version?: number | undefined;

}): void;
relativePath(absolutePath: string): string;
absolutePath(relativePath: string): string;
toRelativePath(uri: string): string;
toUri(relativePath: string): string;
file: {
absolutePath: string;
uri: string;
type: import("../types").SourceCodeType.JSON;

@@ -181,3 +181,3 @@ version?: number | undefined;

validateJSON?: import("../types").ValidateJSON<import("../types").SourceCodeType.JSON> | undefined;
} & import("../types").Dependencies & {
} & import("../types").AugmentedDependencies & {
settings: import("../types").Settings<import("../types").Schema>;

@@ -198,3 +198,3 @@ }): Partial<Partial<{

onCodePathStart(file: {
absolutePath: string;
uri: string;
type: import("../types").SourceCodeType.JSON;

@@ -206,3 +206,3 @@ version?: number | undefined;

onCodePathEnd(file: {
absolutePath: string;
uri: string;
type: import("../types").SourceCodeType.JSON;

@@ -209,0 +209,0 @@ version?: number | undefined;

@@ -26,8 +26,8 @@ "use strict";

const file = context.file;
const absolutePath = file.absolutePath;
const relativePath = context.relativePath(absolutePath);
const fileUri = file.uri;
const relativePath = context.toRelativePath(fileUri);
const ast = file.ast;
const isLocaleFile = relativePath.startsWith('locales/');
const isDefaultTranslationsFile = absolutePath.endsWith('.default.json') || absolutePath.endsWith('.default.schema.json');
const isSchemaTranslationFile = absolutePath.endsWith('.schema.json');
const isDefaultTranslationsFile = fileUri.endsWith('.default.json') || fileUri.endsWith('.default.schema.json');
const isSchemaTranslationFile = fileUri.endsWith('.schema.json');
if (!isLocaleFile || isDefaultTranslationsFile || ast instanceof Error) {

@@ -34,0 +34,0 @@ // No need to lint a file that isn't a translation file, we return an

@@ -21,3 +21,3 @@ "use strict";

create(context) {
if (context.relativePath(context.file.absolutePath) !== 'layout/theme.liquid') {
if (context.toRelativePath(context.file.uri) !== 'layout/theme.liquid') {
return {};

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

@@ -80,3 +80,3 @@ "use strict";

if (schemaLocales) {
message += ` or '${context.relativePath(context.file.absolutePath)}'`;
message += ` or '${context.toRelativePath(context.file.uri)}'`;
}

@@ -83,0 +83,0 @@ context.report({

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

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

@@ -29,0 +29,0 @@ return {};

@@ -22,3 +22,3 @@ "use strict";

// We ignore non-`locales/` json files.
const relativePath = context.relativePath(context.file.absolutePath);
const relativePath = context.toRelativePath(context.file.uri);
if (!relativePath.startsWith('locales/'))

@@ -25,0 +25,0 @@ return {};

@@ -31,6 +31,6 @@ import { Offense } from '../types';

}): void;
relativePath(absolutePath: string): string;
absolutePath(relativePath: string): string;
toRelativePath(uri: string): string;
toUri(relativePath: string): string;
file: {
absolutePath: string;
uri: string;
type: import("../types").SourceCodeType.LiquidHtml;

@@ -42,3 +42,3 @@ version?: number | undefined;

validateJSON?: import("../types").ValidateJSON<import("../types").SourceCodeType.LiquidHtml> | undefined;
} & import("../types").Dependencies & {
} & import("../types").AugmentedDependencies & {
settings: import("../types").Settings<import("../types").Schema>;

@@ -119,3 +119,3 @@ }): Partial<Partial<{

onCodePathStart(file: {
absolutePath: string;
uri: string;
type: import("../types").SourceCodeType.LiquidHtml;

@@ -127,3 +127,3 @@ version?: number | undefined;

onCodePathEnd(file: {
absolutePath: string;
uri: string;
type: import("../types").SourceCodeType.LiquidHtml;

@@ -130,0 +130,0 @@ version?: number | undefined;

@@ -8,7 +8,7 @@ "use strict";

const disabledChecks = new Map();
function determineRanges(absolutePath, value, position) {
function determineRanges(uri, value, position) {
const [_, command, checksJoined] = value.trim().match(/^(?:theme\-check\-(disable|enable)) ?(.*)/) || [];
const checks = checksJoined ? checksJoined.split(/,[ ]*/) : [SPECIFIC_CHECK_NOT_DEFINED];
checks.forEach((check) => {
const disabledRanges = disabledChecks.get(absolutePath);
const disabledRanges = disabledChecks.get(uri);
if (command === 'disable') {

@@ -43,3 +43,3 @@ if (!disabledRanges.has(check)) {

async onCodePathStart() {
disabledChecks.set(file.absolutePath, new Map());
disabledChecks.set(file.uri, new Map());
},

@@ -50,3 +50,3 @@ async LiquidRawTag(node) {

}
determineRanges(file.absolutePath, node.body.value, node.position);
determineRanges(file.uri, node.body.value, node.position);
},

@@ -57,3 +57,3 @@ async LiquidTag(node) {

}
determineRanges(file.absolutePath, node.markup, node.position);
determineRanges(file.uri, node.markup, node.position);
},

@@ -64,9 +64,9 @@ }),

const ranges = [SPECIFIC_CHECK_NOT_DEFINED, offense.check].flatMap((check) => {
if (!disabledChecks.has(offense.absolutePath)) {
if (!disabledChecks.has(offense.uri)) {
return [];
}
if (!disabledChecks.get(offense.absolutePath).has(check)) {
if (!disabledChecks.get(offense.uri).has(check)) {
return [];
}
return disabledChecks.get(offense.absolutePath).get(check);
return disabledChecks.get(offense.uri).get(check);
});

@@ -73,0 +73,0 @@ return ranges.some((range) => offense.start.index >= range.from && (!range.to || offense.end.index <= range.to));

@@ -17,3 +17,3 @@ "use strict";

for (const sourceCode of sourceCodes) {
const sourceCodeOffenses = fixableOffenses.filter((offense) => offense.absolutePath === sourceCode.absolutePath);
const sourceCodeOffenses = fixableOffenses.filter((offense) => offense.uri === sourceCode.uri);
if (sourceCodeOffenses.length === 0) {

@@ -20,0 +20,0 @@ continue;

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

import { AbsolutePath, CheckDefinition, Config } from './types';
export declare function isIgnored(absolutePath: AbsolutePath, config: Config, checkDef?: CheckDefinition): boolean;
import { UriString, CheckDefinition, Config } from './types';
export declare function isIgnored(uri: UriString, config: Config, checkDef?: CheckDefinition): boolean;

@@ -5,8 +5,8 @@ "use strict";

const minimatch_1 = require("minimatch");
function isIgnored(absolutePath, config, checkDef) {
function isIgnored(uri, config, checkDef) {
const ignorePatterns = [...checkIgnorePatterns(checkDef, config), ...asArray(config.ignore)].map((pattern) => pattern
.replace(/^\//, config.root + '/') // "absolute patterns" are config.root matches
.replace(/^\//, config.rootUri + '/') // "absolute patterns" are config.rootUri matches
.replace(/^([^\/])/, '**/$1') // "relative patterns" are "**/${pattern}"
.replace(/\/\*$/, '/**'));
return ignorePatterns.some((pattern) => (0, minimatch_1.minimatch)(absolutePath, pattern));
return ignorePatterns.some((pattern) => (0, minimatch_1.minimatch)(uri, pattern));
}

@@ -13,0 +13,0 @@ exports.isIgnored = isIgnored;

import { Config, Dependencies, Offense, Theme } from './types';
export * from './AbstractFileSystem';
export * from './AugmentedThemeDocset';
export * from './checks';
export * from './context-utils';
export * from './find-root';
export * from './fixes';
export * from './ignore';
export * from './json';
export * as path from './path';
export * from './to-source-code';
export * from './types';
export * from './checks';
export * from './to-source-code';
export * from './json';
export * from './ignore';
export * from './utils/error';
export * from './utils/indexBy';
export * from './utils/memo';
export * from './utils/types';
export * from './utils/memo';
export * from './utils/indexBy';
export declare function check(sourceCodes: Theme, config: Config, dependencies: Dependencies): Promise<Offense[]>;
export declare function check(theme: Theme, config: Config, injectedDependencies: Dependencies): Promise<Offense[]>;

@@ -29,28 +29,44 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.check = void 0;
const types_1 = require("./types");
const visitors_1 = require("./visitors");
exports.check = exports.path = void 0;
const AugmentedThemeDocset_1 = require("./AugmentedThemeDocset");
const JSONValidator_1 = require("./JSONValidator");
const context_utils_1 = require("./context-utils");
const disabled_checks_1 = require("./disabled-checks");
const ignore_1 = require("./ignore");
const path = __importStar(require("./path"));
const types_1 = require("./types");
const utils_1 = require("./utils");
const ignore_1 = require("./ignore");
const AugmentedThemeDocset_1 = require("./AugmentedThemeDocset");
const JSONValidator_1 = require("./JSONValidator");
const visitors_1 = require("./visitors");
__exportStar(require("./AbstractFileSystem"), exports);
__exportStar(require("./AugmentedThemeDocset"), exports);
__exportStar(require("./checks"), exports);
__exportStar(require("./context-utils"), exports);
__exportStar(require("./find-root"), exports);
__exportStar(require("./fixes"), exports);
__exportStar(require("./ignore"), exports);
__exportStar(require("./json"), exports);
exports.path = __importStar(require("./path"));
__exportStar(require("./to-source-code"), exports);
__exportStar(require("./types"), exports);
__exportStar(require("./checks"), exports);
__exportStar(require("./to-source-code"), exports);
__exportStar(require("./json"), exports);
__exportStar(require("./ignore"), exports);
__exportStar(require("./utils/error"), exports);
__exportStar(require("./utils/indexBy"), exports);
__exportStar(require("./utils/memo"), exports);
__exportStar(require("./utils/types"), exports);
__exportStar(require("./utils/memo"), exports);
__exportStar(require("./utils/indexBy"), exports);
const defaultErrorHandler = (_error) => {
// Silently ignores errors by default.
};
async function check(sourceCodes, config, dependencies) {
async function check(theme, config, injectedDependencies) {
const pipelines = [];
const offenses = [];
const { fs } = injectedDependencies;
const { rootUri } = config;
const dependencies = {
...injectedDependencies,
fileExists: (0, context_utils_1.makeFileExists)(fs),
fileSize: (0, context_utils_1.makeFileSize)(fs),
getDefaultLocale: (0, context_utils_1.makeGetDefaultLocale)(fs, rootUri),
getDefaultTranslations: (0, context_utils_1.makeGetDefaultTranslations)(fs, theme, rootUri),
getDefaultSchemaLocale: (0, context_utils_1.makeGetDefaultSchemaLocale)(fs, rootUri),
getDefaultSchemaTranslations: (0, context_utils_1.makeGetDefaultSchemaTranslations)(fs, theme, rootUri),
};
const { DisabledChecksVisitor, isDisabled } = (0, disabled_checks_1.createDisabledChecksModule)();

@@ -69,7 +85,7 @@ let validateJSON;

case types_1.SourceCodeType.JSON: {
const files = filesOfType(type, sourceCodes);
const files = filesOfType(type, theme);
const checkDefs = checksOfType(type, config.checks);
for (const file of files) {
for (const checkDef of checkDefs) {
if ((0, ignore_1.isIgnored)(file.absolutePath, config, checkDef))
if ((0, ignore_1.isIgnored)(file.uri, config, checkDef))
continue;

@@ -83,7 +99,7 @@ const check = createCheck(checkDef, file, config, offenses, dependencies, validateJSON);

case types_1.SourceCodeType.LiquidHtml: {
const files = filesOfType(type, sourceCodes);
const files = filesOfType(type, theme);
const checkDefs = [DisabledChecksVisitor, ...checksOfType(type, config.checks)];
for (const file of files) {
for (const checkDef of checkDefs) {
if ((0, ignore_1.isIgnored)(file.absolutePath, config, checkDef))
if ((0, ignore_1.isIgnored)(file.uri, config, checkDef))
continue;

@@ -109,4 +125,4 @@ const check = createCheck(checkDef, file, config, offenses, dependencies, validateJSON);

settings: createSettings(checkSettings, check.meta.schema),
absolutePath: (relativePath) => path.join(config.root, relativePath),
relativePath: (absolutePath) => path.relative(absolutePath, config.root),
toUri: (relativePath) => path.join(config.rootUri, relativePath),
toRelativePath: (uri) => path.relative(uri, config.rootUri),
report(problem) {

@@ -118,3 +134,3 @@ var _a;

message: problem.message,
absolutePath: file.absolutePath,
uri: file.uri,
severity: (_a = checkSettings === null || checkSettings === void 0 ? void 0 : checkSettings.severity) !== null && _a !== void 0 ? _a : check.meta.severity,

@@ -121,0 +137,0 @@ start: (0, utils_1.getPosition)(file.source, problem.startIndex),

@@ -15,3 +15,3 @@ "use strict";

this.validate = async (sourceCode, jsonString) => {
const jsonTextDocument = vscode_json_languageservice_1.TextDocument.create('file:' + sourceCode.absolutePath, 'json', 0, jsonString);
const jsonTextDocument = vscode_json_languageservice_1.TextDocument.create('file:' + sourceCode.uri, 'json', 0, jsonString);
const jsonDocument = this.service.parseJSONDocument(jsonTextDocument);

@@ -18,0 +18,0 @@ const diagnostics = await this.service.doValidation(jsonTextDocument, jsonDocument, {

@@ -1,3 +0,8 @@

import { RelativePath, AbsolutePath } from './types';
export declare function relative(absolutePath: AbsolutePath, root: AbsolutePath): RelativePath;
export declare function join(...paths: string[]): string;
import { RelativePath, UriString } from './types';
import { URI } from 'vscode-uri';
export declare function relative(uri: UriString, rootUri: UriString): RelativePath;
export declare function join(rootUri: UriString, ...paths: string[]): string;
export declare function normalize(uri: UriString | URI): UriString;
export declare function dirname(uri: UriString): UriString;
export declare function basename(uri: UriString, ext?: string): string;
export declare function fsPath(uri: UriString): string;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.join = exports.relative = void 0;
function relative(absolutePath, root) {
return absolutePath.replace(root, '').replace(/^\//, '');
exports.fsPath = exports.basename = exports.dirname = exports.normalize = exports.join = exports.relative = void 0;
const vscode_uri_1 = require("vscode-uri");
function relative(uri, rootUri) {
return uri
.replace(rootUri, '')
.replace(/\\\\/g, '/') // We expect forward slash paths (windows path get normalized)
.replace(/^\/+/, '');
}
exports.relative = relative;
function join(...paths) {
return paths.map(removeTrailingSlash).join('/');
function join(rootUri, ...paths) {
const root = vscode_uri_1.URI.parse(rootUri);
return normalize(vscode_uri_1.Utils.joinPath(root, ...paths));
}
exports.join = join;
function removeTrailingSlash(path) {
return path.replace(/\/+$/, '');
function normalize(uri) {
if (!vscode_uri_1.URI.isUri(uri)) {
uri = vscode_uri_1.URI.parse(uri);
}
return uri.toString(true);
}
exports.normalize = normalize;
function dirname(uri) {
return normalize(vscode_uri_1.Utils.dirname(vscode_uri_1.URI.parse(uri)));
}
exports.dirname = dirname;
function basename(uri, ext) {
return vscode_uri_1.URI.parse(uri)
.path.split(/(\\|\/)/g)
.pop()
.replace(ext ? new RegExp(`${ext}$`) : '', '');
}
exports.basename = basename;
function fsPath(uri) {
return vscode_uri_1.URI.parse(uri).fsPath;
}
exports.fsPath = fsPath;
//# sourceMappingURL=path.js.map

@@ -5,2 +5,2 @@ import toJSON from 'json-to-ast';

export declare function toJSONAST(source: string): Error | toJSON.ValueNode;
export declare function toSourceCode(absolutePath: string, source: string, version?: number): LiquidSourceCode | JSONSourceCode;
export declare function toSourceCode(uri: string, source: string, version?: number): LiquidSourceCode | JSONSourceCode;
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -9,2 +32,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

const json_to_ast_1 = __importDefault(require("json-to-ast"));
const path = __importStar(require("./path"));
const types_1 = require("./types");

@@ -30,7 +54,7 @@ const error_1 = require("./utils/error");

exports.toJSONAST = toJSONAST;
function toSourceCode(absolutePath, source, version) {
const isLiquid = absolutePath.endsWith('.liquid');
function toSourceCode(uri, source, version) {
const isLiquid = uri.endsWith('.liquid');
if (isLiquid) {
return {
absolutePath: normalize(absolutePath),
uri: path.normalize(uri),
source,

@@ -44,3 +68,3 @@ type: types_1.SourceCodeType.LiquidHtml,

return {
absolutePath: normalize(absolutePath),
uri: path.normalize(uri),
source,

@@ -54,5 +78,2 @@ type: types_1.SourceCodeType.JSON,

exports.toSourceCode = toSourceCode;
function normalize(path) {
return path.replace(/\\/g, '/');
}
//# sourceMappingURL=to-source-code.js.map

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

import { StringCorrector, JSONCorrector } from './fixes';
import { AbstractFileSystem, UriString } from './AbstractFileSystem';
import { ThemeDocset, JsonValidationSet } from './types/theme-liquid-docs';

@@ -16,4 +17,4 @@ export * from './types/theme-liquid-docs';

export type SourceCode<T = SourceCodeType> = T extends SourceCodeType ? {
/** A normalized absolute path to the file. Assumes forwards slashes. */
absolutePath: string;
/** A normalized uri the file. */
uri: string;
/** The type is used as a discriminant for type narrowing */

@@ -54,5 +55,5 @@ type: T;

};
/** A vscode-uri string. */
export type { UriString };
/** Assumes forward slashes for simplicity internally */
export type AbsolutePath = string;
/** Assumes forward slashes for simplicity internally */
export type RelativePath = string;

@@ -73,3 +74,3 @@ export type ChecksSettings = {

checks: CheckDefinition<SourceCodeType, Schema>[];
root: AbsolutePath;
rootUri: string;
ignore?: string[];

@@ -203,8 +204,3 @@ onError?: (error: Error) => void;

export interface Dependencies {
getDefaultTranslations(): Promise<Translations>;
getDefaultLocale(): Promise<string>;
getDefaultSchemaLocale(): Promise<string>;
getDefaultSchemaTranslations(): Promise<Translations>;
fileExists(absolutePath: string): Promise<boolean>;
fileSize?(absolutePath: string): Promise<number>;
fs: AbstractFileSystem;
themeDocset?: ThemeDocset;

@@ -218,10 +214,18 @@ jsonValidationSet?: JsonValidationSet;

}[]>;
export interface AugmentedDependencies extends Dependencies {
fileExists: (uri: UriString) => Promise<boolean>;
fileSize: (uri: UriString) => Promise<number>;
getDefaultLocale: () => Promise<string>;
getDefaultSchemaLocale: () => Promise<string>;
getDefaultTranslations(): Promise<Translations>;
getDefaultSchemaTranslations(): Promise<Translations>;
}
type StaticContextProperties<T extends SourceCodeType> = T extends SourceCodeType ? {
report(problem: Problem<T>): void;
relativePath(absolutePath: AbsolutePath): RelativePath;
absolutePath(relativePath: RelativePath): AbsolutePath;
toRelativePath(uri: UriString): RelativePath;
toUri(relativePath: RelativePath): UriString;
file: SourceCode<T>;
validateJSON?: ValidateJSON<T>;
} : never;
export type Context<T extends SourceCodeType, S extends Schema = Schema> = T extends SourceCodeType ? StaticContextProperties<T> & Dependencies & {
export type Context<T extends SourceCodeType, S extends Schema = Schema> = T extends SourceCodeType ? StaticContextProperties<T> & AugmentedDependencies & {
settings: Settings<S>;

@@ -332,3 +336,3 @@ } : never;

message: string;
absolutePath: string;
uri: string;
severity: Severity;

@@ -335,0 +339,0 @@ start: Position;

@@ -6,11 +6,11 @@ "use strict";

async function doesFileExist(context, relativePath) {
const absolutePath = context.absolutePath(relativePath);
return await context.fileExists(absolutePath);
const uri = context.toUri(relativePath);
return await context.fileExists(uri);
}
exports.doesFileExist = doesFileExist;
async function doesFileExceedThreshold(context, relativePath, thresholdInBytes) {
const absolutePath = context.absolutePath(relativePath);
const uri = context.toUri(relativePath);
if (!context.fileSize)
return [false, 0];
const fileSize = await context.fileSize(absolutePath);
const fileSize = await context.fileSize(uri);
return [fileSize > thresholdInBytes, fileSize];

@@ -17,0 +17,0 @@ }

{
"name": "@shopify/theme-check-common",
"version": "2.9.2",
"version": "3.0.0",
"license": "MIT",

@@ -36,3 +36,4 @@ "main": "dist/index.js",

"minimatch": "^9.0.1",
"vscode-json-languageservice": "^5.3.10"
"vscode-json-languageservice": "^5.3.10",
"vscode-uri": "^3.0.7"
},

@@ -39,0 +40,0 @@ "devDependencies": {

@@ -12,3 +12,3 @@ <h1 align="center" style="position: relative;" >

Theme Check is available [to code editors that support the Language Server Protocol](https://github.com/Shopify/theme-check/wiki).
Theme Check is available [to code editors that support the Language Server Protocol](https://github.com/Shopify/theme-tools/wiki).

@@ -15,0 +15,0 @@ You may be interested by the sibling modules:

@@ -38,3 +38,3 @@ import { describe, it, expect } from 'vitest';

message: `Theme app extension blocks cannot contain '${tag}' tags`,
absolutePath: '/blocks/app.liquid',
uri: 'file:///blocks/app.liquid',
severity: 0,

@@ -75,3 +75,3 @@ start: expectedStart,

message: `Theme app extension blocks cannot contain '${tag}' tags`,
absolutePath: '/blocks/app.liquid',
uri: 'file:///blocks/app.liquid',
severity: 0,

@@ -78,0 +78,0 @@ start: expectedStart,

@@ -1,5 +0,4 @@

import { expect, describe, it } from 'vitest';
import { describe, expect, it } from 'vitest';
import { AssetSizeAppBlockCSS } from '.';
import { check, MockTheme } from '../../test';
import { SchemaProp } from '../../types';

@@ -24,28 +23,19 @@ describe('Module: AssetSizeAppBlockCSS', () => {

it('should skip the check if context.fileSize is undefined', async () => {
const context = {
fileSize: undefined,
};
const offenses = await check(extensionFiles, [AssetSizeAppBlockCSS], context);
expect(offenses).toHaveLength(0);
});
it('should report an offense if CSS is larger than threshold', async () => {
const CustomAssetSizeAppBlockCSS = {
...AssetSizeAppBlockCSS,
meta: {
...AssetSizeAppBlockCSS.meta,
schema: {
thresholdInBytes: SchemaProp.number(1),
const offenses = await check(
extensionFiles,
[AssetSizeAppBlockCSS],
{},
{
AssetSizeAppBlockCSS: {
enabled: true,
thresholdInBytes: 1,
},
},
};
);
const offenses = await check(extensionFiles, [CustomAssetSizeAppBlockCSS]);
expect(offenses).toHaveLength(1);
expect(offenses[0]).toMatchObject({
message: `The file size for 'app.css' (19 B) exceeds the configured threshold (1 B)`,
absolutePath: '/blocks/app.liquid',
uri: 'file:///blocks/app.liquid',
start: { index: 51 },

@@ -72,3 +62,3 @@ end: { index: 58 },

message: `'nonexistent.css' does not exist.`,
absolutePath: '/blocks/app.liquid',
uri: 'file:///blocks/app.liquid',
start: { index: 57 },

@@ -95,3 +85,3 @@ end: { index: 72 },

message: `'nonexistent.css' does not exist.`,
absolutePath: '/blocks/app.liquid',
uri: 'file:///blocks/app.liquid',
start: { index: 57 },

@@ -98,0 +88,0 @@ end: { index: 72 },

@@ -1,5 +0,4 @@

import { expect, describe, it } from 'vitest';
import { describe, expect, it } from 'vitest';
import { AssetSizeAppBlockJavaScript } from '.';
import { check, MockTheme } from '../../test';
import { SchemaProp } from '../../types';

@@ -24,28 +23,19 @@ describe('Module: AssetSizeAppBlockJavaScript', () => {

it('should skip the check if context.fileSize is undefined', async () => {
const context = {
fileSize: undefined,
};
const offenses = await check(extensionFiles, [AssetSizeAppBlockJavaScript], context);
expect(offenses).toHaveLength(0);
});
it('should report an offense if JavaScript is larger than threshold', async () => {
const CustomAssetSizeAppBlockJavaScript = {
...AssetSizeAppBlockJavaScript,
meta: {
...AssetSizeAppBlockJavaScript.meta,
schema: {
thresholdInBytes: SchemaProp.number(1),
const offenses = await check(
extensionFiles,
[AssetSizeAppBlockJavaScript],
{},
{
AssetSizeAppBlockJavaScript: {
enabled: true,
thresholdInBytes: 1,
},
},
};
);
const offenses = await check(extensionFiles, [CustomAssetSizeAppBlockJavaScript]);
expect(offenses).toHaveLength(1);
expect(offenses[0]).toMatchObject({
message: `The file size for 'app.js' (29 B) exceeds the configured threshold (1 B)`,
absolutePath: '/blocks/app.liquid',
uri: 'file:///blocks/app.liquid',
start: { index: 51 },

@@ -72,3 +62,3 @@ end: { index: 57 },

message: `'nonexistent.js' does not exist.`,
absolutePath: '/blocks/app.liquid',
uri: 'file:///blocks/app.liquid',
start: { index: 57 },

@@ -75,0 +65,0 @@ end: { index: 71 },

@@ -47,28 +47,19 @@ import { vi, expect, describe, it, afterEach } from 'vitest';

it('should skip the check if context.fileSize is undefined', async () => {
const context = {
fileSize: undefined,
};
const offenses = await check(extensionFiles, [AssetSizeCSS], context);
expect(offenses).toHaveLength(0);
});
it('should report an offense if CSS is larger than threshold', async () => {
const CustomAssetSizeCSS = {
...AssetSizeCSS,
meta: {
...AssetSizeCSS.meta,
schema: {
thresholdInBytes: SchemaProp.number(1),
const offenses = await check(
extensionFiles,
[AssetSizeCSS],
{},
{
AssetSizeCSS: {
enabled: true,
thresholdInBytes: 1,
},
},
};
);
const offenses = await check(extensionFiles, [CustomAssetSizeCSS]);
expect(offenses).toHaveLength(1);
expect(offenses[0]).toMatchObject({
message: 'The CSS file size exceeds the threshold of 1 bytes',
absolutePath: '/templates/index.liquid',
uri: 'file:///templates/index.liquid',
start: { index: 51 },

@@ -81,12 +72,13 @@ end: { index: 80 },

vi.mocked(hasRemoteAssetSizeExceededThreshold).mockReturnValue(Promise.resolve(true));
const CustomAssetSizeCSS = {
...AssetSizeCSS,
meta: {
...AssetSizeCSS.meta,
schema: {
thresholdInBytes: SchemaProp.number(1),
const offenses = await check(
httpTest,
[AssetSizeCSS],
{},
{
AssetSizeCSS: {
enabled: true,
thresholdInBytes: 1,
},
},
};
const offenses = await check(httpTest, [CustomAssetSizeCSS]);
);

@@ -96,3 +88,3 @@ expect(offenses).toHaveLength(1);

message: 'The CSS file size exceeds the threshold of 1 bytes',
absolutePath: '/templates/index.liquid',
uri: 'file:///templates/index.liquid',
start: { index: 51 },

@@ -136,18 +128,18 @@ end: { index: 122 },

const CustomAssetSizeCSS = {
...AssetSizeCSS,
meta: {
...AssetSizeCSS.meta,
schema: {
thresholdInBytes: SchemaProp.number(2),
const offenses = await check(
extensionFiles,
[AssetSizeCSS],
{},
{
AssetSizeCSS: {
enabled: true,
thresholdInBytes: 2,
},
},
};
);
const offenses = await check(extensionFiles, [CustomAssetSizeCSS]);
expect(offenses).toHaveLength(2);
expect(offenses[0]).toMatchObject({
message: 'The CSS file size exceeds the threshold of 2 bytes',
absolutePath: '/templates/index.liquid',
uri: 'file:///templates/index.liquid',
start: { index: 48 },

@@ -158,3 +150,3 @@ end: { index: 89 },

message: 'The CSS file size exceeds the threshold of 2 bytes',
absolutePath: '/templates/index.liquid',
uri: 'file:///templates/index.liquid',
start: { index: 107 },

@@ -161,0 +153,0 @@ end: { index: 128 },

@@ -59,12 +59,13 @@ import { expect, describe, it, afterEach, vi } from 'vitest';

vi.mocked(hasRemoteAssetSizeExceededThreshold).mockReturnValue(Promise.resolve(true));
const CustomAssetSizeJavaScript = {
...AssetSizeJavaScript,
meta: {
...AssetSizeJavaScript.meta,
schema: {
thresholdInBytes: SchemaProp.number(1),
const offenses = await check(
httpTest,
[AssetSizeJavaScript],
{},
{
AssetSizeJavaScript: {
enabled: true,
thresholdInBytes: 1,
},
},
};
const offenses = await check(httpTest, [CustomAssetSizeJavaScript]);
);

@@ -75,3 +76,3 @@ expect(offenses).toHaveLength(1);

'JavaScript on every page load exceeds compressed size threshold (1 Bytes), consider using the import on interaction pattern.',
absolutePath: '/templates/index.liquid',
uri: 'file:///templates/index.liquid',
start: { index: 52 },

@@ -89,14 +90,14 @@ end: { index: 121 },

it('should report an offense if JavaScript is larger than threshold', async () => {
const CustomAssetSizeJavaScript = {
...AssetSizeJavaScript,
meta: {
...AssetSizeJavaScript.meta,
schema: {
thresholdInBytes: SchemaProp.number(2),
const offenses = await check(
theme,
[AssetSizeJavaScript],
{},
{
AssetSizeJavaScript: {
enabled: true,
thresholdInBytes: 2,
},
},
};
);
const offenses = await check(theme, [CustomAssetSizeJavaScript]);
expect(offenses).toHaveLength(1);

@@ -106,3 +107,3 @@ expect(offenses[0]).toMatchObject({

'JavaScript on every page load exceeds compressed size threshold (2 Bytes), consider using the import on interaction pattern.',
absolutePath: '/templates/index.liquid',
uri: 'file:///templates/index.liquid',
start: { index: 52 },

@@ -144,14 +145,14 @@ end: { index: 80 },

const CustomAssetSizeCSS = {
...AssetSizeJavaScript,
meta: {
...AssetSizeJavaScript.meta,
schema: {
thresholdInBytes: SchemaProp.number(2),
const offenses = await check(
extensionFiles,
[AssetSizeJavaScript],
{},
{
AssetSizeJavaScript: {
enabled: true,
thresholdInBytes: 2,
},
},
};
);
const offenses = await check(extensionFiles, [CustomAssetSizeCSS]);
expect(offenses).toHaveLength(2);

@@ -161,3 +162,3 @@ expect(offenses[0]).toMatchObject({

'JavaScript on every page load exceeds compressed size threshold (2 Bytes), consider using the import on interaction pattern.',
absolutePath: '/templates/index.liquid',
uri: 'file:///templates/index.liquid',
start: { index: 48 },

@@ -169,3 +170,3 @@ end: { index: 84 },

'JavaScript on every page load exceeds compressed size threshold (2 Bytes), consider using the import on interaction pattern.',
absolutePath: '/templates/index.liquid',
uri: 'file:///templates/index.liquid',
start: { index: 102 },

@@ -172,0 +173,0 @@ end: { index: 123 },

@@ -73,3 +73,3 @@ import { expect, describe, it } from 'vitest';

message: "The translation for 'hello.world' is missing",
absolutePath: `/locales/pt-BR${prefix}.json`,
uri: `file:///locales/pt-BR${prefix}.json`,
});

@@ -104,7 +104,7 @@

message: "A default translation for 'hello' does not exist",
absolutePath: `/locales/pt-BR${prefix}.json`,
uri: `file:///locales/pt-BR${prefix}.json`,
});
expect(offenses).to.containOffense({
message: "The translation for 'hello.world' is missing",
absolutePath: `/locales/pt-BR${prefix}.json`,
uri: `file:///locales/pt-BR${prefix}.json`,
});

@@ -141,11 +141,11 @@

message: "A default translation for 'hello.monde' does not exist",
absolutePath: `/locales/fr${prefix}.json`,
uri: `file:///locales/fr${prefix}.json`,
});
expect(offenses).to.containOffense({
message: "A default translation for 'hello.mundo.hola' does not exist",
absolutePath: `/locales/es-ES${prefix}.json`,
uri: `file:///locales/es-ES${prefix}.json`,
});
expect(offenses).to.containOffense({
message: "The translation for 'hello.world' is missing",
absolutePath: `/locales/fr${prefix}.json`,
uri: `file:///locales/fr${prefix}.json`,
});

@@ -152,0 +152,0 @@

@@ -33,9 +33,9 @@ import { PropertyNode } from 'json-to-ast';

const file = context.file;
const absolutePath = file.absolutePath;
const relativePath = context.relativePath(absolutePath);
const fileUri = file.uri;
const relativePath = context.toRelativePath(fileUri);
const ast = file.ast;
const isLocaleFile = relativePath.startsWith('locales/');
const isDefaultTranslationsFile =
absolutePath.endsWith('.default.json') || absolutePath.endsWith('.default.schema.json');
const isSchemaTranslationFile = absolutePath.endsWith('.schema.json');
fileUri.endsWith('.default.json') || fileUri.endsWith('.default.schema.json');
const isSchemaTranslationFile = fileUri.endsWith('.schema.json');

@@ -42,0 +42,0 @@ if (!isLocaleFile || isDefaultTranslationsFile || ast instanceof Error) {

@@ -22,3 +22,3 @@ import { expect, describe, it } from 'vitest';

message: "'assets/logo.png' does not exist",
absolutePath: '/snippets/snippet.liquid',
uri: 'file:///snippets/snippet.liquid',
});

@@ -40,3 +40,3 @@ });

message: "'assets/styles.css' does not exist",
absolutePath: '/snippets/snippet.liquid',
uri: 'file:///snippets/snippet.liquid',
});

@@ -58,3 +58,3 @@ });

message: "'assets/styles.css' does not exist",
absolutePath: '/snippets/snippet.liquid',
uri: 'file:///snippets/snippet.liquid',
});

@@ -61,0 +61,0 @@ });

@@ -17,3 +17,3 @@ import { expect, describe, it } from 'vitest';

message: "'snippets/missing.liquid' does not exist",
absolutePath: '/snippets/snippet.liquid',
uri: 'file:///snippets/snippet.liquid',
start: { index: 51, line: 2, character: 18 },

@@ -32,3 +32,3 @@ end: { index: 60, line: 2, character: 27 },

message: "'snippets/missing.liquid' does not exist",
absolutePath: '/snippets/snippet.liquid',
uri: 'file:///snippets/snippet.liquid',
start: { index: 11, line: 0, character: 11 },

@@ -47,3 +47,3 @@ end: { index: 20, line: 0, character: 20 },

message: "'sections/missing.liquid' does not exist",
absolutePath: '/sections/section.liquid',
uri: 'file:///sections/section.liquid',
start: { index: 11, line: 0, character: 11 },

@@ -50,0 +50,0 @@ end: { index: 20, line: 0, character: 20 },

@@ -23,3 +23,3 @@ // src/checks/required-layout-theme-object/index.ts

create(context) {
if (context.relativePath(context.file.absolutePath) !== 'layout/theme.liquid') {
if (context.toRelativePath(context.file.uri) !== 'layout/theme.liquid') {
return {};

@@ -26,0 +26,0 @@ }

@@ -56,3 +56,3 @@ import { TranslationKeyExists } from '.';

message: "'key' does not have a matching entry in 'locales/en.default.json'",
absolutePath: '/code.liquid',
uri: 'file:///code.liquid',
});

@@ -98,3 +98,3 @@ });

"'this.does.not.exist' does not have a matching entry in 'locales/en.default.json' or 'code.liquid'",
absolutePath: '/code.liquid',
uri: 'file:///code.liquid',
start: { index: 20, line: 1, character: 3 },

@@ -125,3 +125,3 @@ end: { index: 41, line: 1, character: 24 },

message: "'this.does.not.exist' does not have a matching entry in 'locales/en.default.json'",
absolutePath: '/code.liquid',
uri: 'file:///code.liquid',
start: { index: 14, line: 1, character: 13 },

@@ -147,3 +147,3 @@ end: { index: 35, line: 1, character: 34 },

message: "'unknownkey' does not have a matching entry in 'locales/en.default.json'",
absolutePath: '/code.liquid',
uri: 'file:///code.liquid',
start: { index: 3, line: 0, character: 3 },

@@ -155,3 +155,3 @@ end: { index: 15, line: 0, character: 15 },

message: "'unknown.nested.key' does not have a matching entry in 'locales/en.default.json'",
absolutePath: '/code.liquid',
uri: 'file:///code.liquid',
start: { index: 68, line: 2, character: 3 },

@@ -177,3 +177,3 @@ end: { index: 88, line: 2, character: 23 },

"'shopify.this.does.not.exist' does not have a matching entry in 'locales/en.default.json'",
absolutePath: '/code.liquid',
uri: 'file:///code.liquid',
start: { index: 48, line: 1, character: 3 },

@@ -180,0 +180,0 @@ end: { index: 77, line: 1, character: 32 },

@@ -91,3 +91,3 @@ import { parseJSON } from '../../json';

if (schemaLocales) {
message += ` or '${context.relativePath(context.file.absolutePath)}'`;
message += ` or '${context.toRelativePath(context.file.uri)}'`;
}

@@ -94,0 +94,0 @@

@@ -40,3 +40,3 @@ import {

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

@@ -43,0 +43,0 @@ return {};

@@ -22,3 +22,3 @@ import { SourceCodeType, JSONCheckDefinition, Severity, Problem, JSONNode } from '../../types';

// We ignore non-`locales/` json files.
const relativePath = context.relativePath(context.file.absolutePath);
const relativePath = context.toRelativePath(context.file.uri);
if (!relativePath.startsWith('locales/')) return {};

@@ -25,0 +25,0 @@

@@ -74,3 +74,3 @@ import lodashSet from 'lodash/set';

message: expect.stringContaining('Expected comma or closing brace'),
absolutePath: `/${DEFAULT_FILE_NAME}`,
uri: `file:///${DEFAULT_FILE_NAME}`,
});

@@ -116,3 +116,3 @@ });

message: 'Incorrect type. Expected "string".',
absolutePath: `/${DEFAULT_FILE_NAME}`,
uri: `file:///${DEFAULT_FILE_NAME}`,
});

@@ -122,3 +122,3 @@ expect(offenses).to.containOffense({

message: 'Incorrect type. Expected "number".',
absolutePath: `/${DEFAULT_FILE_NAME}`,
uri: `file:///${DEFAULT_FILE_NAME}`,
});

@@ -128,3 +128,3 @@ expect(offenses).to.containOffense({

message: 'Value is above the maximum of 50.',
absolutePath: `/${DEFAULT_FILE_NAME}`,
uri: `file:///${DEFAULT_FILE_NAME}`,
});

@@ -131,0 +131,0 @@ });

import { Position } from '@shopify/liquid-html-parser';
import { LiquidCheckDefinition, Offense } from '../types';
import { LiquidCheckDefinition, Offense, UriString } from '../types';
type AbsolutePath = string;
type CheckName = string;
type DisabledChecksMap = Map<AbsolutePath, Map<CheckName, { from: number; to?: number }[]>>;
type DisabledChecksMap = Map<UriString, Map<CheckName, { from: number; to?: number }[]>>;

@@ -13,3 +12,3 @@ export function createDisabledChecksModule() {

function determineRanges(absolutePath: string, value: string, position: Position) {
function determineRanges(uri: string, value: string, position: Position) {
const [_, command, checksJoined] =

@@ -21,3 +20,3 @@ value.trim().match(/^(?:theme\-check\-(disable|enable)) ?(.*)/) || [];

checks.forEach((check) => {
const disabledRanges = disabledChecks.get(absolutePath)!;
const disabledRanges = disabledChecks.get(uri)!;

@@ -54,3 +53,3 @@ if (command === 'disable') {

async onCodePathStart() {
disabledChecks.set(file.absolutePath, new Map());
disabledChecks.set(file.uri, new Map());
},

@@ -63,3 +62,3 @@

determineRanges(file.absolutePath, node.body.value, node.position);
determineRanges(file.uri, node.body.value, node.position);
},

@@ -72,3 +71,3 @@

determineRanges(file.absolutePath, node.markup, node.position);
determineRanges(file.uri, node.markup, node.position);
},

@@ -80,9 +79,9 @@ }),

const ranges = [SPECIFIC_CHECK_NOT_DEFINED, offense.check].flatMap((check) => {
if (!disabledChecks.has(offense.absolutePath)) {
if (!disabledChecks.has(offense.uri)) {
return [];
}
if (!disabledChecks.get(offense.absolutePath)!.has(check)) {
if (!disabledChecks.get(offense.uri)!.has(check)) {
return [];
}
return disabledChecks.get(offense.absolutePath)!.get(check)!;
return disabledChecks.get(offense.uri)!.get(check)!;
});

@@ -89,0 +88,0 @@

@@ -16,3 +16,3 @@ import { expect, describe, it } from 'vitest';

fix: (corrector) => corrector.insert(2, 'nanana'),
absolutePath: '/a.liquid',
uri: 'file:///a.liquid',
check: 'Mock Check',

@@ -34,3 +34,3 @@ message: 'Mock check message',

],
absolutePath: '/a.liquid',
uri: 'file:///a.liquid',
check: 'Mock Check',

@@ -48,3 +48,3 @@ message: 'Mock check message',

},
absolutePath: '/b.json',
uri: 'file:///b.json',
check: 'Mock Check',

@@ -66,3 +66,3 @@ message: 'Mock check message',

],
absolutePath: '/b.json',
uri: 'file:///b.json',
check: 'Mock Check',

@@ -69,0 +69,0 @@ message: 'Mock check message',

@@ -25,5 +25,3 @@ import { FixApplicator, Offense, SourceCodeType, Theme } from '../types';

for (const sourceCode of sourceCodes) {
const sourceCodeOffenses = fixableOffenses.filter(
(offense) => offense.absolutePath === sourceCode.absolutePath,
);
const sourceCodeOffenses = fixableOffenses.filter((offense) => offense.uri === sourceCode.uri);

@@ -30,0 +28,0 @@ if (sourceCodeOffenses.length === 0) {

import { expect, describe, it } from 'vitest';
import { isIgnored } from './ignore';
import { AbsolutePath, CheckDefinition, Config, SourceCodeType } from './types';
import { UriString, CheckDefinition, Config, SourceCodeType } from './types';

@@ -23,3 +23,3 @@ const checkDef: CheckDefinition = {

const result = isIgnored(
absolutePath('snippets/foo.liquid'),
toUri('snippets/foo.liquid'),
config({

@@ -36,3 +36,3 @@ checkIgnore: [],

const result = isIgnored(
absolutePath('snippets/foo.liquid'),
toUri('snippets/foo.liquid'),
config({

@@ -50,3 +50,3 @@ checkIgnore: ['*.liquid'],

const result = isIgnored(
absolutePath('snippets/foo.liquid'),
toUri('snippets/foo.liquid'),
config({

@@ -64,3 +64,3 @@ checkIgnore: ['!snippets/*'],

const result = isIgnored(
absolutePath('snippets/foo.liquid'),
toUri('snippets/foo.liquid'),
config({

@@ -78,3 +78,3 @@ checkIgnore: ['snippets/*.liquid'],

const result = isIgnored(
absolutePath('snippets/foo.liquid'),
toUri('snippets/foo.liquid'),
config({

@@ -92,3 +92,3 @@ checkIgnore: ['other-snippets/*.liquid'],

const result = isIgnored(
absolutePath('snippets/foo.liquid'),
toUri('snippets/foo.liquid'),
config({

@@ -106,3 +106,3 @@ checkIgnore: [],

const result = isIgnored(
absolutePath('snippets/foo.liquid'),
toUri('snippets/foo.liquid'),
config({

@@ -120,3 +120,3 @@ checkIgnore: ['snippets/*.liquid'],

const result = isIgnored(
absolutePath('node_modules/some-library/foo.liquid'),
toUri('node_modules/some-library/foo.liquid'),
config({

@@ -134,3 +134,3 @@ checkIgnore: ['node_modules/*'],

const result = isIgnored(
absolutePath('some-library/node_modules/foo.liquid'),
toUri('some-library/node_modules/foo.liquid'),
config({

@@ -149,3 +149,3 @@ // any kind of node_modules are ignored

const result = isIgnored(
absolutePath('some-library/node_modules/foo.liquid'),
toUri('some-library/node_modules/foo.liquid'),
config({

@@ -164,3 +164,3 @@ // any kind of node_modules are ignored

const result = isIgnored(
absolutePath('some-library/node_modules/foo.liquid'),
toUri('some-library/node_modules/foo.liquid'),
config({

@@ -179,3 +179,3 @@ // only /root/node_modules/* is ignored, other ones aren't

const result = isIgnored(
absolutePath('layout/theme.liquid'),
toUri('layout/theme.liquid'),
config({

@@ -191,4 +191,4 @@ checkIgnore: [],

function absolutePath(relativePath: string): AbsolutePath {
return `/path/to/${relativePath}`;
function toUri(relativePath: string): UriString {
return `file:/path/to/${relativePath}`;
}

@@ -212,5 +212,5 @@

checks: [],
root: '/path/to',
rootUri: 'file:/path/to',
ignore: globalIgnore,
};
}

@@ -1,13 +0,9 @@

import { AbsolutePath, CheckDefinition, Config } from './types';
import { UriString, CheckDefinition, Config } from './types';
import { minimatch } from 'minimatch';
export function isIgnored(
absolutePath: AbsolutePath,
config: Config,
checkDef?: CheckDefinition,
): boolean {
export function isIgnored(uri: UriString, config: Config, checkDef?: CheckDefinition): boolean {
const ignorePatterns = [...checkIgnorePatterns(checkDef, config), ...asArray(config.ignore)].map(
(pattern) =>
pattern
.replace(/^\//, config.root + '/') // "absolute patterns" are config.root matches
.replace(/^\//, config.rootUri + '/') // "absolute patterns" are config.rootUri matches
.replace(/^([^\/])/, '**/$1') // "relative patterns" are "**/${pattern}"

@@ -17,3 +13,3 @@ .replace(/\/\*$/, '/**'), // "/*" patterns are really "/**"

return ignorePatterns.some((pattern) => minimatch(absolutePath, pattern));
return ignorePatterns.some((pattern) => minimatch(uri, pattern));
}

@@ -20,0 +16,0 @@

@@ -0,2 +1,16 @@

import { AugmentedThemeDocset } from './AugmentedThemeDocset';
import { JSONValidator } from './JSONValidator';
import {
makeFileExists,
makeFileSize,
makeGetDefaultLocale,
makeGetDefaultSchemaLocale,
makeGetDefaultSchemaTranslations,
makeGetDefaultTranslations,
} from './context-utils';
import { createDisabledChecksModule } from './disabled-checks';
import { isIgnored } from './ignore';
import * as path from './path';
import {
AugmentedDependencies,
Check,

@@ -23,21 +37,20 @@ CheckDefinition,

} from './types';
import { visitLiquid, visitJSON } from './visitors';
import { createDisabledChecksModule } from './disabled-checks';
import * as path from './path';
import { getPosition } from './utils';
import { isIgnored } from './ignore';
import { AugmentedThemeDocset } from './AugmentedThemeDocset';
import { JSONValidator } from './JSONValidator';
import { visitJSON, visitLiquid } from './visitors';
export * from './AbstractFileSystem';
export * from './AugmentedThemeDocset';
export * from './checks';
export * from './context-utils';
export * from './find-root';
export * from './fixes';
export * from './ignore';
export * from './json';
export * as path from './path';
export * from './to-source-code';
export * from './types';
export * from './checks';
export * from './to-source-code';
export * from './json';
export * from './ignore';
export * from './utils/error';
export * from './utils/indexBy';
export * from './utils/memo';
export * from './utils/types';
export * from './utils/memo';
export * from './utils/indexBy';

@@ -49,8 +62,20 @@ const defaultErrorHandler = (_error: Error): void => {

export async function check(
sourceCodes: Theme,
theme: Theme,
config: Config,
dependencies: Dependencies,
injectedDependencies: Dependencies,
): Promise<Offense[]> {
const pipelines: Promise<void>[] = [];
const offenses: Offense[] = [];
const { fs } = injectedDependencies;
const { rootUri } = config;
const dependencies: AugmentedDependencies = {
...injectedDependencies,
fileExists: makeFileExists(fs),
fileSize: makeFileSize(fs),
getDefaultLocale: makeGetDefaultLocale(fs, rootUri),
getDefaultTranslations: makeGetDefaultTranslations(fs, theme, rootUri),
getDefaultSchemaLocale: makeGetDefaultSchemaLocale(fs, rootUri),
getDefaultSchemaTranslations: makeGetDefaultSchemaTranslations(fs, theme, rootUri),
};
const { DisabledChecksVisitor, isDisabled } = createDisabledChecksModule();

@@ -74,7 +99,7 @@ let validateJSON: ValidateJSON<SourceCodeType> | undefined;

case SourceCodeType.JSON: {
const files = filesOfType(type, sourceCodes);
const files = filesOfType(type, theme);
const checkDefs = checksOfType(type, config.checks);
for (const file of files) {
for (const checkDef of checkDefs) {
if (isIgnored(file.absolutePath, config, checkDef)) continue;
if (isIgnored(file.uri, config, checkDef)) continue;
const check = createCheck(checkDef, file, config, offenses, dependencies, validateJSON);

@@ -87,7 +112,7 @@ pipelines.push(checkJSONFile(check, file));

case SourceCodeType.LiquidHtml: {
const files = filesOfType(type, sourceCodes);
const files = filesOfType(type, theme);
const checkDefs = [DisabledChecksVisitor, ...checksOfType(type, config.checks)];
for (const file of files) {
for (const checkDef of checkDefs) {
if (isIgnored(file.absolutePath, config, checkDef)) continue;
if (isIgnored(file.uri, config, checkDef)) continue;
const check = createCheck(checkDef, file, config, offenses, dependencies, validateJSON);

@@ -121,4 +146,4 @@ pipelines.push(checkLiquidFile(check, file));

settings: createSettings(checkSettings, check.meta.schema),
absolutePath: (relativePath) => path.join(config.root, relativePath),
relativePath: (absolutePath) => path.relative(absolutePath, config.root),
toUri: (relativePath) => path.join(config.rootUri, relativePath),
toRelativePath: (uri) => path.relative(uri, config.rootUri),
report(problem: Problem<T>): void {

@@ -129,3 +154,3 @@ offenses.push({

message: problem.message,
absolutePath: file.absolutePath,
uri: file.uri,
severity: checkSettings?.severity ?? check.meta.severity,

@@ -132,0 +157,0 @@ start: getPosition(file.source, problem.startIndex),

@@ -35,8 +35,3 @@ import { LanguageService, TextDocument, getLanguageService } from 'vscode-json-languageservice';

public validate: ValidateJSON<SourceCodeType> = async (sourceCode, jsonString) => {
const jsonTextDocument = TextDocument.create(
'file:' + sourceCode.absolutePath,
'json',
0,
jsonString,
);
const jsonTextDocument = TextDocument.create('file:' + sourceCode.uri, 'json', 0, jsonString);
const jsonDocument = this.service.parseJSONDocument(jsonTextDocument);

@@ -43,0 +38,0 @@ const diagnostics = await this.service.doValidation(jsonTextDocument, jsonDocument, {

@@ -1,13 +0,36 @@

import { RelativePath, AbsolutePath } from './types';
import { RelativePath, UriString } from './types';
import { URI, Utils } from 'vscode-uri';
export function relative(absolutePath: AbsolutePath, root: AbsolutePath): RelativePath {
return absolutePath.replace(root, '').replace(/^\//, '');
export function relative(uri: UriString, rootUri: UriString): RelativePath {
return uri
.replace(rootUri, '')
.replace(/\\\\/g, '/') // We expect forward slash paths (windows path get normalized)
.replace(/^\/+/, '');
}
export function join(...paths: string[]): string {
return paths.map(removeTrailingSlash).join('/');
export function join(rootUri: UriString, ...paths: string[]): string {
const root = URI.parse(rootUri);
return normalize(Utils.joinPath(root, ...paths));
}
function removeTrailingSlash(path: string): string {
return path.replace(/\/+$/, '');
export function normalize(uri: UriString | URI): UriString {
if (!URI.isUri(uri)) {
uri = URI.parse(uri);
}
return uri.toString(true);
}
export function dirname(uri: UriString): UriString {
return normalize(Utils.dirname(URI.parse(uri)));
}
export function basename(uri: UriString, ext?: string): string {
return URI.parse(uri)
.path.split(/(\\|\/)/g)
.pop()!
.replace(ext ? new RegExp(`${ext}$`) : '', '');
}
export function fsPath(uri: UriString): string {
return URI.parse(uri).fsPath;
}

@@ -45,3 +45,3 @@ import { expect, describe, it } from 'vitest';

message: 'Coffee is better',
absolutePath: '/snippets/cookies.liquid',
uri: 'file:///snippets/cookies.liquid',
check: '',

@@ -48,0 +48,0 @@ severity: 0,

@@ -45,3 +45,3 @@ import { expect, describe, it } from 'vitest';

message: 'Coffee is better',
absolutePath: '/snippets/cookies.liquid',
uri: 'file:///snippets/cookies.liquid',
check: '',

@@ -48,0 +48,0 @@ severity: 0,

import { expect, describe, it } from 'vitest';
import { Offense, SourceCodeType } from '../types';
import { path } from '..';
const rootUri = 'file:///';
describe('Module: containOffense', () => {

@@ -45,3 +48,3 @@ const offenses: Offense[] = [

message: `The translation for 'hello.world' is missing`,
absolutePath: 'locales/en.json',
uri: 'file:///locales/en.json',
severity: 0,

@@ -54,3 +57,3 @@ });

message: `The translation for 'hello.world' is missing`,
absolutePath: 'locales/en.json',
uri: 'file:///locales/en.json',
severity: 1,

@@ -62,7 +65,7 @@ });

function buildOffense(message: string, absolutePath: string): Offense {
function buildOffense(message: string, relativePath: string): Offense {
return {
type: SourceCodeType.LiquidHtml,
message,
absolutePath,
uri: path.join(rootUri, relativePath),
check: '',

@@ -69,0 +72,0 @@ severity: 0,

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

export * from './MockFileSystem';
export * from './MockTheme';
export * from './test-helper';
import {
check as coreCheck,
autofix as coreAutofix,
applyFixToString,
toSourceCode,
Offense,
CheckDefinition,
ChecksSettings,
Config,
SourceCodeType,
Theme,
autofix as coreAutofix,
check as coreCheck,
createCorrector,
Dependencies,
FixApplicator,
JSONCorrector,
JSONSourceCode,
LiquidSourceCode,
CheckDefinition,
Offense,
parseJSON,
recommended,
SourceCodeType,
StringCorrector,
JSONCorrector,
FixApplicator,
createCorrector,
Dependencies,
ChecksSettings,
parseJSON,
Theme,
toSourceCode,
} from '../index';
import * as path from '../path';
import { MockFileSystem } from './MockFileSystem';
import { MockTheme } from './MockTheme';
export { StringCorrector, JSONCorrector };
export { JSONCorrector, StringCorrector };
/**
* @example
* {
* 'theme/layout.liquid': `
* <html>
* {{ content_for_page }}
* </html>
* `,
* 'snippets/snip.liquid': `
* <b>'hello world'</b>
* `,
* }
*/
export type MockTheme = {
[relativePath in string]: string;
};
const rootUri = path.normalize('file:/');
export function getTheme(themeDesc: MockTheme): Theme {
return Object.entries(themeDesc)
.map(([relativePath, source]) => toSourceCode(asAbsolutePath(relativePath), source))
.map(([relativePath, source]) => toSourceCode(toUri(relativePath), source))
.filter((x): x is LiquidSourceCode | JSONSourceCode => x !== undefined);

@@ -59,15 +47,9 @@ }

checks: checks,
root: '/',
rootUri: 'file:/',
};
const defaultTranslationsFileRelativePath = 'locales/en.default.json';
const defaultSchemaTranslationsFileRelativePath = 'locales/en.default.schema.json';
const defaultMockDependencies = {
async fileSize(absolutePath: string) {
const relativePath = absolutePath.replace(/^\//, '');
return themeDesc[relativePath].length;
},
async fileExists(absolutePath: string) {
const relativePath = absolutePath.replace(/^\//, '');
return themeDesc[relativePath] !== undefined;
},
fs: new MockFileSystem(themeDesc),
async getDefaultTranslations() {

@@ -79,10 +61,2 @@ return parseJSON(themeDesc[defaultTranslationsFileRelativePath] || '{}', {});

},
async getDefaultLocale() {
return defaultTranslationsFileRelativePath.match(/locales\/(.*)\.default\.json$/)?.[1]!;
},
async getDefaultSchemaLocale() {
return defaultSchemaTranslationsFileRelativePath.match(
/locales\/(.*)\.default\.schema\.json$/,
)?.[1]!;
},
themeDocset: {

@@ -223,3 +197,3 @@ async filters() {

const offenses = await check({ [fileName]: sourceCode }, [checkDef], mockDependencies);
return offenses.filter((offense) => offense.absolutePath === `/${fileName}`);
return offenses.filter((offense) => offense.uri === path.join(rootUri, fileName));
}

@@ -234,3 +208,3 @@

const offenses = await check({ [fileName]: sourceCode }, [checkDef], mockDependencies);
return offenses.filter((offense) => offense.absolutePath === `/${fileName}`);
return offenses.filter((offense) => offense.uri === path.join(rootUri, fileName));
}

@@ -243,3 +217,3 @@

const stringApplicator: FixApplicator = async (sourceCode, fixes) => {
fixed[asRelative(sourceCode.absolutePath)] = applyFixToString(sourceCode.source, fixes);
fixed[asRelative(sourceCode.uri)] = applyFixToString(sourceCode.source, fixes);
};

@@ -259,3 +233,3 @@

? themeDescOrSource
: themeDescOrSource[asRelative(offense.absolutePath)];
: themeDescOrSource[asRelative(offense.uri)];
const corrector = createCorrector(offense.type, source);

@@ -273,3 +247,3 @@ offense.fix?.(corrector as any);

? themeDescOrSource
: themeDescOrSource[asRelative(offense.absolutePath)];
: themeDescOrSource[asRelative(offense.uri)];
return offense.suggest?.map((suggestion) => {

@@ -286,3 +260,3 @@ const corrector = createCorrector(offense.type, source);

return offenses.map((offense) => {
const relativePath = offense.absolutePath.substring(1);
const relativePath = path.relative(offense.uri, rootUri);
const source = theme[relativePath];

@@ -298,8 +272,8 @@ const {

function asAbsolutePath(relativePath: string) {
return '/' + relativePath;
function toUri(relativePath: string) {
return path.join(rootUri, relativePath);
}
function asRelative(absolutePath: string) {
return absolutePath.replace(/^\//, '');
function asRelative(uri: string) {
return path.relative(path.normalize(uri), rootUri);
}

@@ -306,0 +280,0 @@

import { toLiquidHtmlAST } from '@shopify/liquid-html-parser';
import toJSON from 'json-to-ast';
import { SourceCodeType, JSONSourceCode, LiquidSourceCode } from './types';
import * as path from './path';
import { JSONSourceCode, LiquidSourceCode, SourceCodeType } from './types';
import { asError } from './utils/error';

@@ -24,11 +25,11 @@

export function toSourceCode(
absolutePath: string,
uri: string,
source: string,
version?: number,
): LiquidSourceCode | JSONSourceCode {
const isLiquid = absolutePath.endsWith('.liquid');
const isLiquid = uri.endsWith('.liquid');
if (isLiquid) {
return {
absolutePath: normalize(absolutePath),
uri: path.normalize(uri),
source,

@@ -41,3 +42,3 @@ type: SourceCodeType.LiquidHtml,

return {
absolutePath: normalize(absolutePath),
uri: path.normalize(uri),
source,

@@ -50,7 +51,1 @@ type: SourceCodeType.JSON,

}
type MaybeWindowsPath = string;
function normalize(path: MaybeWindowsPath): string {
return path.replace(/\\/g, '/');
}

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

import { StringCorrector, JSONCorrector } from './fixes';
import { AbstractFileSystem, UriString } from './AbstractFileSystem';

@@ -31,4 +32,4 @@ import { ThemeDocset, JsonValidationSet } from './types/theme-liquid-docs';

? {
/** A normalized absolute path to the file. Assumes forwards slashes. */
absolutePath: string;
/** A normalized uri the file. */
uri: string;
/** The type is used as a discriminant for type narrowing */

@@ -86,4 +87,4 @@ type: T;

/** Assumes forward slashes for simplicity internally */
export type AbsolutePath = string;
/** A vscode-uri string. */
export type { UriString };

@@ -114,3 +115,3 @@ /** Assumes forward slashes for simplicity internally */

checks: CheckDefinition<SourceCodeType, Schema>[];
root: AbsolutePath;
rootUri: string; // e.g. file:///path-to-root
ignore?: string[];

@@ -279,8 +280,3 @@ onError?: (error: Error) => void;

export interface Dependencies {
getDefaultTranslations(): Promise<Translations>;
getDefaultLocale(): Promise<string>;
getDefaultSchemaLocale(): Promise<string>;
getDefaultSchemaTranslations(): Promise<Translations>;
fileExists(absolutePath: string): Promise<boolean>;
fileSize?(absolutePath: string): Promise<number>;
fs: AbstractFileSystem;
themeDocset?: ThemeDocset;

@@ -295,7 +291,16 @@ jsonValidationSet?: JsonValidationSet;

export interface AugmentedDependencies extends Dependencies {
fileExists: (uri: UriString) => Promise<boolean>;
fileSize: (uri: UriString) => Promise<number>;
getDefaultLocale: () => Promise<string>;
getDefaultSchemaLocale: () => Promise<string>;
getDefaultTranslations(): Promise<Translations>;
getDefaultSchemaTranslations(): Promise<Translations>;
}
type StaticContextProperties<T extends SourceCodeType> = T extends SourceCodeType
? {
report(problem: Problem<T>): void;
relativePath(absolutePath: AbsolutePath): RelativePath;
absolutePath(relativePath: RelativePath): AbsolutePath;
toRelativePath(uri: UriString): RelativePath;
toUri(relativePath: RelativePath): UriString;
file: SourceCode<T>;

@@ -307,3 +312,3 @@ validateJSON?: ValidateJSON<T>;

export type Context<T extends SourceCodeType, S extends Schema = Schema> = T extends SourceCodeType
? StaticContextProperties<T> & Dependencies & { settings: Settings<S> }
? StaticContextProperties<T> & AugmentedDependencies & { settings: Settings<S> }
: never;

@@ -435,3 +440,3 @@

message: string;
absolutePath: string;
uri: string;
severity: Severity;

@@ -438,0 +443,0 @@ start: Position;

@@ -8,4 +8,4 @@ import { Context, SourceCodeType, Schema, RelativePath } from '../types';

): Promise<boolean> {
const absolutePath = context.absolutePath(relativePath);
return await context.fileExists(absolutePath);
const uri = context.toUri(relativePath);
return await context.fileExists(uri);
}

@@ -18,5 +18,5 @@

): Promise<[exceeds: boolean, fileSize: number]> {
const absolutePath = context.absolutePath(relativePath);
const uri = context.toUri(relativePath);
if (!context.fileSize) return [false, 0];
const fileSize = await context.fileSize(absolutePath);
const fileSize = await context.fileSize(uri);
return [fileSize > thresholdInBytes, fileSize];

@@ -23,0 +23,0 @@ }

{
"extends": "./tsconfig.json",
"exclude": ["**/*.spec.ts", "**/test/*.ts"],
"exclude": ["**/*.spec.ts"],
"references": [

@@ -5,0 +5,0 @@ { "path": "../liquid-html-parser/tsconfig.build.json" }

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

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

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