eslint-plugin-project-structure
Version:
Powerful ESLint plugin with rules to help you achieve a scalable, consistent, and well-structured project. Create your own framework! Define your folder structure, file composition, advanced naming conventions, and create independent modules. Take your pr
1,561 lines (1,488 loc) • 118 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
createFileComposition: () => createFileComposition,
createFolderStructure: () => createFolderStructure,
createIndependentModules: () => createIndependentModules,
projectStructureParser: () => parser,
projectStructurePlugin: () => projectStructurePlugin
});
module.exports = __toCommonJS(index_exports);
// src/parser.ts
var parser = {
meta: { name: "projectStructureParser" },
parseForESLint: () => ({
ast: {
type: "Program",
start: 0,
end: 0,
loc: {
start: {
line: 0,
column: 0
},
end: {
line: 0,
column: 0
}
},
tokens: [],
comments: [],
range: [0, 0],
sourceType: "module",
body: []
},
scopeManager: null,
visitorKeys: null
})
};
var parser_default = parser;
module.exports = parser;
// src/rules/fileComposition/fileComposition.ts
var import_utils13 = require("@typescript-eslint/utils");
// src/rules/fileComposition/fileComposition.consts.ts
var ESLINT_ERRORS = {
invalidName: `\u{1F525} Invalid '{{selectorType}}' name, allowed formats = {{formatWithoutReferences}} \u{1F525}`,
invalidPosition: `\u{1F525} Invalid '{{selectorType}}' position. It is located in line {{currentLine}} but should be in line {{correctLine}}. \u{1F525}`,
prohibitedSelectorRoot: `\u{1F525} The use of '{{selectorType}}' is prohibited in the root of the file. \u{1F525}{{error}}`,
prohibitedSelectorNested: `\u{1F525} The use of nested '{{selectorType}}' is prohibited in this file. \u{1F525}{{error}}`,
prohibitedSelectorExport: `\u{1F525} Exporting '{{selectorType}}' is prohibited in this file. \u{1F525}{{error}}`,
rootSelectorsLimits: "\u{1F525} The limit for the given selectors in the root of the file has been exceeded. \u{1F525}\n{{error}}\n\n"
};
// src/rules/fileComposition/helpers/getFileCompositionConfig/getFileCompositionConfig.ts
var import_path3 = __toESM(require("path"));
// src/helpers/getProjectRoot.ts
var import_path = __toESM(require("path"));
var getProjectRoot = ({
cwd,
projectRootConfig
}) => {
const dirnameSplit = (0, import_path.dirname)(__filename).split(import_path.sep);
const indexOfNodeModules = dirnameSplit.indexOf("node_modules");
if (indexOfNodeModules === -1) return cwd;
const rootPath = dirnameSplit.slice(0, indexOfNodeModules).join(import_path.sep);
if (!projectRootConfig) return rootPath;
return import_path.default.resolve(rootPath, projectRootConfig);
};
// src/helpers/isCorrectPattern.ts
var import_micromatch = __toESM(require("micromatch"));
var isCorrectPattern = ({
input,
pattern
}) => {
if (typeof pattern === "string") return import_micromatch.default.every(input, pattern);
return pattern.some((p) => import_micromatch.default.every(input, p));
};
// src/helpers/readConfigFile/readConfigFile.ts
var import_fs = require("fs");
var import_comment_json = require("comment-json");
var import_js_yaml = require("js-yaml");
// src/errors/getInvalidConfigFileError.ts
var getInvalidConfigFileError = (configPath) => new Error(
`\u{1F525} '${configPath}' file cannot be read or has an incorrect extension. \u{1F525}`
);
// src/helpers/readConfigFile/helpers/getConfigPath.ts
var import_path2 = __toESM(require("path"));
// src/errors/getMissingConfigFileError.ts
var getMissingConfigFileError = (key) => new Error(`\u{1F525} Missing configuration file '${key}'. \u{1F525}`);
// src/helpers/readConfigFile/helpers/getConfigPath.ts
var getConfigPath = ({
settings,
key,
cwd
}) => {
const configPath = settings[key];
if (!configPath) throw getMissingConfigFileError(key);
return import_path2.default.resolve(getProjectRoot({ cwd }), configPath);
};
// src/helpers/readConfigFile/readConfigFile.ts
var readConfigFile = ({
key,
settings,
options,
cwd
}) => {
if (options) return options;
const configPath = getConfigPath({
key,
settings,
cwd
});
let config = void 0;
if (configPath.endsWith("json") || configPath.endsWith("jsonc")) {
config = (0, import_comment_json.parse)((0, import_fs.readFileSync)(configPath, "utf-8"));
} else {
config = (0, import_js_yaml.load)((0, import_fs.readFileSync)(configPath, "utf8"));
}
if (!config) throw getInvalidConfigFileError(configPath);
return config;
};
// src/helpers/validateConfig.ts
var import_jsonschema = require("jsonschema");
// src/errors/getInvalidConfigError.ts
var getInvalidConfigError = (errors) => new Error(
errors.reduce(
(acc, stack) => acc + `\u{1F525} ${stack.replace("instance", "configuration")}.
`,
"\u{1F525} Invalid configuration file:\n"
)
);
// src/helpers/validateConfig.ts
var validateConfig = ({
config,
schema
}) => {
const errors = (0, import_jsonschema.validate)(config, schema, {
nestedErrors: true
}).errors.filter(({ stack }) => !stack.includes("$schema"));
if (!errors.length) return;
throw getInvalidConfigError(errors.map(({ stack }) => stack));
};
// src/rules/fileComposition/helpers/getFileCompositionConfig/getFileCompositionConfig.consts.ts
var FILE_COMPOSITION_SCHEMA = {
$schema: "http://json-schema.org/draft-07/schema#",
definitions: {
SelectorType: {
type: "string",
default: "",
enum: [
"class",
"variable",
"variableExpression",
"propertyDefinition",
"arrowFunction",
"function",
"type",
"interface",
"enum"
]
},
Selector: {
oneOf: [
{ $ref: "#/definitions/SelectorType" },
{
type: "object",
default: { type: "variableExpression", limitTo: "" },
additionalProperties: false,
properties: {
type: {
type: "string",
default: "variableExpression",
enum: ["variableExpression"]
},
limitTo: {
oneOf: [
{ type: "string", default: "" },
{
type: "array",
default: [],
items: { type: "string" }
}
]
}
},
required: ["type", "limitTo"]
}
]
},
RootSelectorsLimits: {
type: "array",
default: [],
items: {
type: "object",
additionalProperties: false,
properties: {
selector: {
oneOf: [
{ $ref: "#/definitions/SelectorType" },
{
type: "array",
default: [],
items: { $ref: "#/definitions/SelectorType" }
}
]
},
limit: {
oneOf: [
{ type: "number" },
{
type: "object",
additionalProperties: false,
properties: {
min: {
type: "number"
},
max: {
type: "number"
}
}
}
]
}
},
required: ["limit", "selector"]
}
},
FileRule: {
type: "object",
default: {},
additionalProperties: false,
properties: {
selector: {
oneOf: [
{ $ref: "#/definitions/Selector" },
{
type: "array",
default: [],
items: { $ref: "#/definitions/Selector" }
}
]
},
scope: {
oneOf: [
{
type: "string",
default: "file",
enum: ["file", "fileExport", "fileRoot", "nestedSelectors"]
},
{
type: "array",
default: [],
items: {
type: "string",
default: "file",
enum: ["file", "fileExport", "fileRoot"]
}
}
]
},
positionIndex: {
oneOf: [
{ type: "number", default: 0 },
{
type: "object",
default: { index: 0, sorting: "az" },
properties: {
index: { type: "number", default: 0 },
sorting: {
type: "string",
default: "az",
enum: ["az", "none"]
}
},
required: ["index"]
}
]
},
filenamePartsToRemove: {
oneOf: [
{ type: "string", default: "" },
{
type: "array",
default: [],
items: { type: "string", default: "" }
}
]
},
format: {
oneOf: [
{ type: "string", default: "" },
{ type: "array", default: [], items: { type: "string" } }
]
}
},
required: ["selector"]
},
CustomErrors: {
type: "object",
default: {},
additionalProperties: false,
properties: {
class: { type: "string", default: "" },
variable: { type: "string", default: "" },
variableExpression: { type: "string", default: "" },
propertyDefinition: { type: "string", default: "" },
function: { type: "string", default: "" },
arrowFunction: { type: "string", default: "" },
type: { type: "string", default: "" },
interface: { type: "string", default: "" },
enum: { type: "string", default: "" }
}
},
FileRules: {
type: "object",
default: {},
additionalProperties: false,
properties: {
filePattern: {
oneOf: [
{ type: "string", default: "" },
{
type: "array",
default: [],
items: {
oneOf: [
{ type: "string", default: "" },
{
type: "array",
default: [],
items: { type: "string" }
}
]
}
}
]
},
allowOnlySpecifiedSelectors: {
oneOf: [
{ type: "boolean", default: true },
{
type: "object",
additionalProperties: false,
default: {
errors: {},
fileRoot: true,
fileExport: true,
file: true
},
properties: {
error: { $ref: "#/definitions/CustomErrors" },
fileRoot: {
oneOf: [
{ type: "boolean", default: true },
{ $ref: "#/definitions/CustomErrors" }
]
},
fileExport: {
oneOf: [
{ type: "boolean", default: true },
{ $ref: "#/definitions/CustomErrors" }
]
},
nestedSelectors: {
oneOf: [
{ type: "boolean", default: true },
{ $ref: "#/definitions/CustomErrors" }
]
}
}
}
]
},
rootSelectorsLimits: { $ref: "#/definitions/RootSelectorsLimits" },
rules: {
type: "array",
default: [],
items: { $ref: "#/definitions/FileRule" }
}
},
required: ["filePattern"]
},
RegexParameters: {
type: "object",
default: {},
additionalProperties: {
type: "string",
default: ""
}
}
},
type: "object",
additionalProperties: false,
properties: {
projectRoot: {
type: "string",
default: "."
},
filesRules: {
type: "array",
default: [],
items: { $ref: "#/definitions/FileRules" }
},
regexParameters: {
$ref: "#/definitions/RegexParameters"
}
},
required: ["filesRules"]
};
// src/rules/fileComposition/helpers/getFileCompositionConfig/getFileCompositionConfig.ts
var getFileCompositionConfig = ({
filename,
settings,
options,
cwd
}) => {
const config = readConfigFile({
key: "project-structure/file-composition-config-path",
settings,
options: options[0],
cwd
});
validateConfig({ config, schema: FILE_COMPOSITION_SCHEMA });
const filenamePath = import_path3.default.relative(
getProjectRoot({ cwd, projectRootConfig: config.projectRoot }),
filename
);
const fileConfig = config.filesRules.find(
({ filePattern }) => isCorrectPattern({ input: filenamePath, pattern: filePattern })
);
return { config, fileConfig };
};
// src/rules/fileComposition/helpers/validateFile/validateFile.ts
var import_path5 = __toESM(require("path"));
// src/rules/fileComposition/helpers/validateFile/helpers/getCurrentScopeData.ts
var getCurrentScopeData = ({
isFileExport,
isFileRoot
}) => {
if (isFileExport)
return {
scope: "fileExport",
errorMessageId: "prohibitedSelectorExport"
};
if (isFileRoot)
return {
scope: "fileRoot",
errorMessageId: "prohibitedSelectorRoot"
};
return {
scope: "nestedSelectors",
errorMessageId: "prohibitedSelectorNested"
};
};
// src/rules/fileComposition/helpers/validateFile/helpers/isCorrectScope.ts
var isCorrectScope = ({
expect,
scope
}) => {
if (scope === "file" || !scope) return true;
if (typeof scope === "string") return scope === expect;
return scope.some((s) => s === expect || s === "file");
};
// src/rules/fileComposition/helpers/validateFile/helpers/isExportedName/isExportedName.ts
var import_utils4 = require("@typescript-eslint/utils");
// src/rules/fileComposition/helpers/validateFile/helpers/isExportedName/helpers/isExportDefault.ts
var import_utils2 = require("@typescript-eslint/utils");
// src/rules/fileComposition/helpers/validateFile/helpers/getProgramFromNode.ts
var import_utils = require("@typescript-eslint/utils");
var getProgramFromNode = (node) => {
if (node.type !== import_utils.TSESTree.AST_NODE_TYPES.Program)
return getProgramFromNode(node.parent);
return node;
};
// src/rules/fileComposition/helpers/validateFile/helpers/isExportedName/helpers/isExportDefault.ts
var isExportDefault = ({
name,
node
}) => {
let currentNode = node;
let currentName = name;
let isExportDefault2 = false;
getProgramFromNode(node).body.forEach((node2) => {
if (node2.type !== import_utils2.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration) return;
if (node2.declaration.type === import_utils2.TSESTree.AST_NODE_TYPES.Identifier && node2.declaration.name === name) {
isExportDefault2 = true;
currentNode = node2.declaration;
return;
}
if (node2.declaration.type === import_utils2.TSESTree.AST_NODE_TYPES.ObjectExpression) {
node2.declaration.properties.forEach((property) => {
if (property.type !== import_utils2.TSESTree.AST_NODE_TYPES.Property) return;
if (property.key.type === import_utils2.TSESTree.AST_NODE_TYPES.Identifier && property.value.type === import_utils2.TSESTree.AST_NODE_TYPES.Identifier && property.value.name === name) {
isExportDefault2 = true;
currentNode = property.key;
currentName = property.key.name;
return;
}
});
}
if (node2.declaration.type === import_utils2.TSESTree.AST_NODE_TYPES.ArrayExpression) {
node2.declaration.elements.forEach((element) => {
if (!element) return;
if (element.type === import_utils2.TSESTree.AST_NODE_TYPES.Identifier && element.name === name) {
isExportDefault2 = true;
currentNode = element;
return;
}
});
}
});
return {
isExportDefault: isExportDefault2,
currentNode,
currentName
};
};
// src/rules/fileComposition/helpers/validateFile/helpers/isExportedName/helpers/isNamedExport.ts
var import_utils3 = require("@typescript-eslint/utils");
var isNamedExport = ({
name,
node
}) => {
let isNamedExport2 = false;
let currentNode = node;
let currentName = name;
getProgramFromNode(node).body.forEach((node2) => {
if (node2.type !== import_utils3.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration) return;
node2.specifiers.forEach((specifier) => {
if (specifier.local.type !== import_utils3.TSESTree.AST_NODE_TYPES.Identifier || specifier.exported.type !== import_utils3.TSESTree.AST_NODE_TYPES.Identifier)
return;
if (specifier.local.name === name && specifier.exported.name !== name) {
isNamedExport2 = true;
currentName = specifier.exported.name;
currentNode = specifier.exported;
return;
}
if (specifier.local.name === name && specifier.exported.name === name) {
isNamedExport2 = true;
currentNode = specifier.local;
return;
}
});
});
return {
isNamedExport: isNamedExport2,
currentName,
currentNode
};
};
// src/rules/fileComposition/helpers/validateFile/helpers/isExportedName/isExportedName.ts
var isExportedName = ({
nodeType,
node,
name
}) => {
if ((nodeType === "ArrowFunctionExpression" || nodeType === "Expression" || nodeType === "VariableDeclarator") && node.parent.parent?.type === import_utils4.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration || node.parent.parent?.type === import_utils4.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration)
return {
isExportName: true,
currentNode: node,
currentName: name
};
if (node.parent.type === import_utils4.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration || node.parent.type === import_utils4.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration)
return {
isExportName: true,
currentNode: node,
currentName: name
};
const namedExport = isNamedExport({ name, node });
const exportDefault = isExportDefault({ name, node });
if (namedExport.isNamedExport)
return {
isExportName: true,
currentNode: namedExport.currentNode,
currentName: namedExport.currentName
};
if (exportDefault.isExportDefault)
return {
isExportName: true,
currentNode: exportDefault.currentNode,
currentName: exportDefault.currentName
};
return {
isExportName: false,
currentName: name,
currentNode: node
};
};
// src/rules/fileComposition/helpers/validateFile/helpers/isNameFromFileRoot.ts
var import_utils5 = require("@typescript-eslint/utils");
var isNameFromFileRoot = ({
nodeType,
node
}) => {
if (nodeType === "ArrowFunctionExpression" || nodeType === "VariableDeclarator" || nodeType === "Expression")
return node.parent.parent?.type === import_utils5.TSESTree.AST_NODE_TYPES.Program || node.parent.parent?.type === import_utils5.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration || node.parent.parent?.type === import_utils5.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration || node.parent.type === import_utils5.TSESTree.AST_NODE_TYPES.Program;
return node.parent.type === import_utils5.TSESTree.AST_NODE_TYPES.Program || node.parent.type === import_utils5.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration || node.parent.type === import_utils5.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration;
};
// src/consts.ts
var SNAKE_CASE_LOWER_RE = /((([a-z]|\d)+_)*([a-z]|\d)+)/;
var SNAKE_CASE_UPPER_RE = /((([A-Z]|\d)+_)*([A-Z]|\d)+)/;
var KEBAB_CASE_RE = /((([a-z]|\d)+-)*([a-z]|\d)+)/;
var CAMEL_CASE_RE = /([a-z]+[A-Z0-9]*[A-Z0-9]*)*/;
var PASCAL_CASE_RE = /([A-Z]+[a-z0-9]*[A-Z0-9]*)*/;
var STRICT_CAMEL_CASE_RE = /[a-z][a-z0-9]*(([A-Z][a-z0-9]+)*[A-Z]?|([a-z0-9]+[A-Z])*|[A-Z])/;
var STRICT_PASCAL_CASE_RE = /[A-Z](([a-z0-9]+[A-Z]?)*)/;
var SNAKE_CASE_LOWER = `${SNAKE_CASE_LOWER_RE}`.replace(/\//g, "");
var SNAKE_CASE_UPPER = `${SNAKE_CASE_UPPER_RE}`.replace(/\//g, "");
var KEBAB_CASE = `${KEBAB_CASE_RE}`.replace(/\//g, "");
var CAMEL_CASE = `${CAMEL_CASE_RE}`.replace(/\//g, "");
var PASCAL_CASE = `${PASCAL_CASE_RE}`.replace(/\//g, "");
var STRICT_CAMEL_CASE = `${STRICT_CAMEL_CASE_RE}`.replace(/\//g, "");
var STRICT_PASCAL_CASE = `${STRICT_PASCAL_CASE_RE}`.replace(/\//g, "");
var RECURSION_LIMIT = 1e3;
var WILDCARD_REGEX = "(([^/]*)+)";
var ESLINT_ERRORS2 = {
error: `{{error}}`
};
var PROJECT_STRUCTURE_CACHE_FILE_NAME = "projectStructure.cache.json";
// src/errors/getInvalidRegexError.ts
var getInvalidRegexError = (regex) => new Error(`\u{1F525} Regex: ${regex} is invalid. \u{1F525}`);
// src/helpers/isRegexInvalid.ts
var isRegexInvalid = (regex) => {
try {
new RegExp(`^${regex}$`, "g");
} catch (_e) {
return true;
}
return false;
};
// src/rules/fileComposition/helpers/validateFile/helpers/isCorrectSelector.ts
var isCorrectSelector = ({
selector,
selectorType,
expressionName
}) => {
if (typeof selector === "string") return selector === selectorType;
if (!Array.isArray(selector)) {
if (!expressionName) return false;
if (typeof selector.limitTo === "string") {
const regexImproved = selector.limitTo.replaceAll("*", WILDCARD_REGEX).replaceAll(`${WILDCARD_REGEX}${WILDCARD_REGEX}`, "*");
if (isRegexInvalid(regexImproved))
throw getInvalidRegexError(regexImproved);
const finalRegex = new RegExp(`^${regexImproved}$`, "g");
return selector.type === selectorType && finalRegex.test(expressionName);
}
return selector.limitTo.some(
(limitTo) => isCorrectSelector({
selector: { type: "variableExpression", limitTo },
selectorType,
expressionName
})
);
}
return selector.some(
(sel) => isCorrectSelector({ selector: sel, selectorType, expressionName })
);
};
// src/rules/fileComposition/helpers/validateFile/helpers/isSelectorAllowed/helpers/getCustomError.ts
var getCustomError = ({
selectorType,
allowOnlySpecifiedSelectors,
scope
}) => {
if (allowOnlySpecifiedSelectors === true) return "";
if (typeof allowOnlySpecifiedSelectors[scope] === "object") {
const scopeErrors = {
...allowOnlySpecifiedSelectors.error,
...allowOnlySpecifiedSelectors[scope]
};
if (!scopeErrors[selectorType]) return "";
return `
${scopeErrors[selectorType]}
`;
}
if (allowOnlySpecifiedSelectors.error?.[selectorType])
return `
${allowOnlySpecifiedSelectors.error[selectorType]}
`;
return "";
};
// src/rules/fileComposition/helpers/validateFile/helpers/isSelectorAllowed/isSelectorAllowed.ts
var isSelectorAllowed = ({
rules,
report,
node,
errorMessageId,
selectorType,
expressionName,
scope,
allowOnlySpecifiedSelectors
}) => {
const isAllowed = rules.map(({ selector }) => selector).flat().some(
(selector) => isCorrectSelector({ selector, selectorType, expressionName })
);
if (isAllowed || !allowOnlySpecifiedSelectors || typeof allowOnlySpecifiedSelectors === "object" && allowOnlySpecifiedSelectors[scope] === false)
return true;
report({
messageId: errorMessageId,
data: {
selectorType,
error: getCustomError({
selectorType,
scope,
allowOnlySpecifiedSelectors
})
},
node
});
return false;
};
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/getFileNameWithoutExtension.ts
var import_path4 = __toESM(require("path"));
var getFileNameWithoutExtension = (filenamePath) => {
const fileNameWithExtension = import_path4.default.basename(filenamePath);
return fileNameWithExtension.substring(
0,
fileNameWithExtension.lastIndexOf(".")
);
};
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/removeFilenameParts.ts
var removeFilenameParts = ({
filenameWithoutExtension,
filenamePartsToRemove
}) => {
if (!filenamePartsToRemove) return filenameWithoutExtension;
const currentFilenamePartsToRemove = typeof filenamePartsToRemove === "string" ? [filenamePartsToRemove] : filenamePartsToRemove;
return currentFilenamePartsToRemove.reduce(
(acc, removePart) => acc.replaceAll(removePart, ""),
filenameWithoutExtension
);
};
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/getFilenameWithoutParts.ts
var getFilenameWithoutParts = ({
filenamePartsToRemove,
filenamePath
}) => {
const filenameWithoutExtension = getFileNameWithoutExtension(filenamePath);
return removeFilenameParts({
filenameWithoutExtension,
filenamePartsToRemove
});
};
// src/helpers/getUpperCaseFirstLetter.ts
var getUpperCaseFirstLetter = (text) => text.charAt(0).toUpperCase() + text.slice(1);
// src/helpers/transformStringToCase.ts
var transformStringToCase = ({
str,
transformTo
}) => {
const toCamelCase = (input) => {
if (input === input.toUpperCase()) {
return input.toLowerCase().replace(/(_[a-z])/g, (_, char) => char.toUpperCase()).replace(/^[A-Z]/, (char) => char.toLowerCase()).replace(/_/g, "");
}
return input.replace(
/([-_][a-z])/gi,
(match) => match.toUpperCase().replace(/[-_]/g, "")
).replace(/^([A-Z])/, (char) => char.toLowerCase()).replace(/_/g, "");
};
const toSnakeCaseLower = (input) => input.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/([A-Za-z])(\d)/g, "$1_$2").replace(/(\d)([A-Za-z])/g, "$1_$2").replace(/[-\s]/g, "_").replace(/_+/g, "_").toLowerCase();
const toKebabCase = (input) => input.replace(
/([a-z\d])([A-Z]+)/g,
(_match, p1, p2) => p1 + "-" + p2.toLowerCase()
).replace(
/([A-Z]+)([A-Z][a-z])/g,
(_match, p1, p2) => `${p1.toLowerCase()}-${p2}`
).replace(
/(\d)([a-zA-Z])/g,
(_match, p1, p2) => `${p1}-${p2.toLowerCase()}`
).replace(
/([a-zA-Z])(\d)/g,
(_match, p1, p2) => `${p1}-${p2}`
).replace(/[_\s]+/g, "-").toLowerCase();
let result;
switch (transformTo) {
case "PascalCase":
result = getUpperCaseFirstLetter(toCamelCase(str));
break;
case "kebab-case":
result = toKebabCase(str);
break;
case "snake_case":
result = toSnakeCaseLower(str);
break;
case "SNAKE_CASE":
result = toSnakeCaseLower(str).toUpperCase();
break;
default:
result = toCamelCase(str);
break;
}
return result;
};
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.consts.ts
var REFERENCES = {
FileName: "{FileName}",
fileName: "{fileName}",
file_name: "{file_name}",
FILE_NAME: "{FILE_NAME}",
camelCase: "{camelCase}",
PascalCase: "{PascalCase}",
strictCamelCase: "{strictCamelCase}",
StrictPascalCase: "{StrictPascalCase}",
snake_case: "{snake_case}",
SNAKE_CASE: "{SNAKE_CASE}"
};
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFormatWithFilenameReferences.ts
var getFormatWithFilenameReferences = ({
filename,
formatWithReferences
}) => formatWithReferences.map(
(pattern) => pattern.replaceAll(
REFERENCES.fileName,
transformStringToCase({
str: filename,
transformTo: "camelCase"
})
).replaceAll(
REFERENCES.FileName,
transformStringToCase({
str: filename,
transformTo: "PascalCase"
})
).replaceAll(
REFERENCES.file_name,
transformStringToCase({
str: filename,
transformTo: "snake_case"
})
).replaceAll(
REFERENCES.FILE_NAME,
transformStringToCase({
str: filename,
transformTo: "SNAKE_CASE"
})
)
);
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getBodyWithoutImports.ts
var import_utils6 = require("@typescript-eslint/utils");
var getBodyWithoutImports = (node) => {
const program = getProgramFromNode(node);
return program.body.filter(
({ type }) => type !== import_utils6.TSESTree.AST_NODE_TYPES.ImportDeclaration
);
};
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.ts
var import_utils8 = require("@typescript-eslint/utils");
// src/rules/fileComposition/helpers/getIdentifierFromExpression.ts
var import_utils7 = require("@typescript-eslint/utils");
var getIdentifierFromExpression = (expression) => {
if (!expression) return;
if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.CallExpression && expression.callee.type === import_utils7.TSESTree.AST_NODE_TYPES.Identifier)
return expression.callee.name;
if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.MemberExpression && expression.object.type === import_utils7.TSESTree.AST_NODE_TYPES.Identifier)
return expression.object.name;
if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.TaggedTemplateExpression && expression.tag.type === import_utils7.TSESTree.AST_NODE_TYPES.Identifier)
return expression.tag.name;
if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.TaggedTemplateExpression && (expression.tag.type === import_utils7.TSESTree.AST_NODE_TYPES.MemberExpression || expression.tag.type === import_utils7.TSESTree.AST_NODE_TYPES.CallExpression))
return getIdentifierFromExpression(expression.tag);
if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.TSAsExpression && (expression.expression.type === import_utils7.TSESTree.AST_NODE_TYPES.CallExpression || expression.expression.type === import_utils7.TSESTree.AST_NODE_TYPES.TaggedTemplateExpression))
return getIdentifierFromExpression(expression.expression);
if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.CallExpression && (expression.callee.type === import_utils7.TSESTree.AST_NODE_TYPES.CallExpression || expression.callee.type === import_utils7.TSESTree.AST_NODE_TYPES.MemberExpression))
return getIdentifierFromExpression(expression.callee);
if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.MemberExpression && expression.object.type === import_utils7.TSESTree.AST_NODE_TYPES.MemberExpression)
return getIdentifierFromExpression(expression.object);
return;
};
// src/rules/fileComposition/helpers/validateFile/validateFile.consts.ts
var SELECTORS = {
VariableDeclarator: "variable",
Expression: "variableExpression",
ClassDeclaration: "class",
FunctionDeclaration: "function",
ArrowFunctionExpression: "arrowFunction",
TSEnumDeclaration: "enum",
TSInterfaceDeclaration: "interface",
TSTypeAliasDeclaration: "type",
PropertyDefinition: "propertyDefinition"
};
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.ts
var getSelectorNamesFromBody = (body) => body.map((node) => {
const currentNode = node.type === import_utils8.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration || node.type === import_utils8.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration ? node.declaration : node;
if (currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.VariableDeclaration && currentNode.declarations[0].id.type === import_utils8.TSESTree.AST_NODE_TYPES.Identifier) {
const expressionName = getIdentifierFromExpression(
currentNode.declarations[0].init
);
if (expressionName) {
return {
selector: "variableExpression",
name: currentNode.declarations[0].id.name,
expressionName,
range: JSON.stringify(currentNode.declarations[0].range)
};
}
return {
selector: currentNode.declarations[0].init?.type === import_utils8.TSESTree.AST_NODE_TYPES.ArrowFunctionExpression ? "arrowFunction" : "variable",
name: currentNode.declarations[0].id.name,
range: JSON.stringify(currentNode.declarations[0].range)
};
}
if ((currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.FunctionDeclaration || currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.ClassDeclaration || currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.TSInterfaceDeclaration || currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.TSTypeAliasDeclaration || currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.TSEnumDeclaration) && currentNode.id?.name) {
const selector = SELECTORS[currentNode.type];
return {
selector,
name: currentNode.id.name,
range: JSON.stringify(currentNode.range)
};
}
return void 0;
}).filter((v) => v !== void 0);
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/isNameValid.ts
var isNameValid = ({
formatWithoutReferences,
name
}) => formatWithoutReferences.some((pattern) => {
if (isRegexInvalid(pattern)) throw getInvalidRegexError(pattern);
const regexp = new RegExp(`^${pattern}$`, "g");
return regexp.test(name);
});
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.ts
var getPositionIndex = ({
positionIndexRules,
bodyWithoutImports,
nodeRange,
positionIndex
}) => {
const selectorNamesFromBody = getSelectorNamesFromBody(bodyWithoutImports);
const positionIndexRulesBody = selectorNamesFromBody.sort((a, b) => {
if (typeof positionIndex === "object" && positionIndex.sorting === "none")
return 0;
return a.name.localeCompare(b.name, void 0, {
numeric: true,
sensitivity: "base"
});
}).map((body) => {
const rule = positionIndexRules.find(
({ format, selector }) => isCorrectSelector({
selector,
selectorType: body.selector,
expressionName: body.expressionName
}) && isNameValid({ formatWithoutReferences: format, name: body.name })
);
if (!rule) return;
return {
...rule,
range: body.range,
expressionName: body.expressionName
};
}).filter((v) => v !== void 0).sort((a, b) => {
if (a.positionIndex < 0 && b.positionIndex >= 0) return 1;
if (a.positionIndex >= 0 && b.positionIndex < 0) return -1;
return a.positionIndex - b.positionIndex;
});
const positionIndexRulesNewOrderPositive = positionIndexRulesBody.filter(({ positionIndex: positionIndex2 }) => positionIndex2 >= 0).map((rule, index) => ({
...rule,
positionIndex: index
}));
const positionIndexRulesNewOrderNegative = positionIndexRulesBody.filter(({ positionIndex: positionIndex2 }) => positionIndex2 < 0).reverse().map((rule, index) => ({
...rule,
positionIndex: selectorNamesFromBody.length - 1 - index
}));
const positionIndexRulesNewOrder = [
...positionIndexRulesNewOrderPositive,
...positionIndexRulesNewOrderNegative
];
const newPositionIndex = positionIndexRulesNewOrder.find(
({ range }) => range === nodeRange
)?.positionIndex;
return newPositionIndex ?? 0;
};
// src/errors/getInvalidReferenceError.ts
var getInvalidReferenceError = ({
invalidReferences,
allowedReferences,
key
}) => new Error(
`\u{1F525} Reference ${invalidReferences.join(", ")} in '${key}' do not exist. \u{1F525}
Allowed references = ${allowedReferences.join(", ")}.
`
);
// src/helpers/getRegexWithoutReferences/helpers/validateReferences/helpers/extractReferencesFromRegex.ts
var extractReferencesFromRegex = ({
filterReferences,
regex
}) => regex.match(/\{([^}]+)\}/g)?.map((match) => match.slice(1, -1)).filter((p) => !filterReferences?.test(p)) ?? [];
// src/helpers/getRegexWithoutReferences/helpers/validateReferences/validateReferences.ts
var validateReferences = ({
allowedReferences,
regex,
filterReferences,
key
}) => {
const references = extractReferencesFromRegex({
regex,
filterReferences
});
const invalidReferences = references.filter((reference) => !allowedReferences.includes(reference)).map((ref) => `{${ref}}`);
if (!invalidReferences.length) return;
throw getInvalidReferenceError({ invalidReferences, allowedReferences, key });
};
// src/helpers/getRegexWithoutReferences/getRegexWithoutReferences.ts
var getRegexWithoutReferences = ({
regex,
regexParameters,
key
}) => {
let currentRegex = regex;
const regexParametersKeys = Object.keys(regexParameters);
validateReferences({
regex,
allowedReferences: regexParametersKeys,
key
});
regexParametersKeys.forEach(
(key2) => currentRegex = currentRegex.replaceAll(
`{${key2}}`,
regexParameters[key2]
)
);
return currentRegex;
};
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/helpers/getDefaultRegexParameters.ts
var getDefaultRegexParameters = ({
fileName,
regexParameters = {}
}) => ({
camelCase: CAMEL_CASE,
PascalCase: PASCAL_CASE,
strictCamelCase: STRICT_CAMEL_CASE,
StrictPascalCase: STRICT_PASCAL_CASE,
snake_case: SNAKE_CASE_LOWER,
SNAKE_CASE: SNAKE_CASE_UPPER,
...regexParameters,
fileName: transformStringToCase({
str: fileName,
transformTo: "camelCase"
}),
FileName: transformStringToCase({
str: fileName,
transformTo: "PascalCase"
}),
file_name: transformStringToCase({
str: fileName,
transformTo: "snake_case"
}),
FILE_NAME: transformStringToCase({
str: fileName,
transformTo: "SNAKE_CASE"
})
});
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/prepareFormat.consts.ts
var DEFAULT_FORMAT = [REFERENCES.camelCase];
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/prepareFormat.ts
var prepareFormat = ({
format,
filenameWithoutParts,
regexParameters
}) => {
let currentFormat = [];
if (!format) currentFormat = DEFAULT_FORMAT;
if (typeof format === "string") currentFormat = [format];
if (Array.isArray(format)) currentFormat = format;
const defaultRegexParameters = getDefaultRegexParameters({
fileName: filenameWithoutParts,
regexParameters
});
const formatWithoutReferences = currentFormat.map(
(regex) => getRegexWithoutReferences({
regex: regex.replaceAll("*", WILDCARD_REGEX).replaceAll(`${WILDCARD_REGEX}${WILDCARD_REGEX}`, "*"),
regexParameters: defaultRegexParameters,
key: "format"
})
);
return {
formatWithoutReferences,
formatWithReferences: currentFormat
};
};
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.ts
var getPositionIndexRules = ({
rules,
regexParameters,
filenamePath
}) => rules.map(({ format, selector, filenamePartsToRemove, positionIndex }) => {
if (positionIndex === void 0) return;
const filenameWithoutParts = getFilenameWithoutParts({
filenamePartsToRemove,
filenamePath
});
return {
positionIndex: typeof positionIndex === "number" ? positionIndex : positionIndex.index,
selector,
format: prepareFormat({
format,
filenameWithoutParts,
regexParameters
}).formatWithoutReferences
};
}).filter((v) => v !== void 0);
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/validatePositionIndex/helpers/getNodePosition.ts
var getNodePosition = ({
bodyWithoutImports,
node
}) => bodyWithoutImports.findIndex(
(bodyNode) => bodyNode.range[0] === node.range[0] && bodyNode.range[1] === node.range[1] || bodyNode.range[0] === node.parent.range[0] && bodyNode.range[1] === node.parent.range[1] || bodyNode.range[0] === node.parent.parent?.range[0] && bodyNode.range[1] === node.parent.parent.range[1]
);
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/validatePositionIndex/validatePositionIndex.ts
var validatePositionIndex = ({
node,
selectorType,
context: { report, sourceCode },
positionIndex,
bodyWithoutImports
}) => {
const nodePosition = getNodePosition({ bodyWithoutImports, node });
if (nodePosition === positionIndex) return;
const nodeToReplace = bodyWithoutImports[positionIndex];
const currentNodePosition = bodyWithoutImports[nodePosition];
report({
messageId: "invalidPosition",
node,
data: {
selectorType,
currentLine: currentNodePosition.loc.start.line,
correctLine: nodeToReplace.loc.start.line
},
fix: (fixer) => [
fixer.replaceText(nodeToReplace, sourceCode.getText(currentNodePosition)),
fixer.replaceText(currentNodePosition, sourceCode.getText(nodeToReplace))
]
});
};
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.ts
var handlePositionIndex = ({
filenamePath,
node,
rules,
regexParameters,
context,
selectorType,
positionIndex
}) => {
const positionIndexRules = getPositionIndexRules({
filenamePath,
rules,
regexParameters
});
const bodyWithoutImports = getBodyWithoutImports(node);
const newPositionIndex = getPositionIndex({
bodyWithoutImports,
positionIndexRules,
nodeRange: JSON.stringify(node.range),
positionIndex
});
validatePositionIndex({
node,
positionIndex: newPositionIndex,
selectorType,
context,
bodyWithoutImports
});
};
// src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.ts
var validateRules = ({
name,
node,
filenamePath,
rules,
errorMessageId,
regexParameters,
expressionName,
allowOnlySpecifiedSelectors,
scope,
nodeNotExported,
context,
context: { report },
allRules,
selectorType
}) => {
if (!isSelectorAllowed({
rules,
scope,
allowOnlySpecifiedSelectors,
node,
selectorType,
report,
errorMessageId,
expressionName
}) || name === "*")
return;
const selectorTypeRules = rules.filter(
({ selector }) => isCorrectSelector({
selectorType,
selector,
expressionName
})
);
const formatWithoutReferences = selectorTypeRules.map(({ format, filenamePartsToRemove, positionIndex }) => {
const filenameWithoutParts = getFilenameWithoutParts({
filenamePartsToRemove,
filenamePath
});
const { formatWithReferences, formatWithoutReferences: formatWithoutReferences2 } = prepareFormat({
format,
filenameWithoutParts,
regexParameters
});
const isValid = isNameValid({
formatWithoutReferences: formatWithoutReferences2,
name
});
if (isValid) {
if (positionIndex === void 0 || scope === "nestedSelectors") return;
return handlePositionIndex({
context,
filenamePath,
node: nodeNotExported ?? node,
rules: allRules,
selectorType,
regexParameters,
positionIndex
});
}
return getFormatWithFilenameReferences({
formatWithReferences,
filename: filenameWithoutParts
});
}).filter((v) => v !== void 0);
if (!formatWithoutReferences.length || formatWithoutReferences.length !== selectorTypeRules.length)
return;
report({
node,
messageId: "invalidName",
data: {
selectorType,
formatWithoutReferences: formatWithoutReferences.flat().join(", ")
}
});
};
// src/rules/fileComposition/helpers/validateFile/validateFile.ts
var validateFile = ({
name,
expressionName,
context,
context: { filename, cwd },
node,
nodeType,
fileConfig,
config
}) => {
if (!fileConfig) return;
const { rules = [], allowOnlySpecifiedSelectors } = fileConfig;
const fileExportRules = rules.filter(
({ scope: scope2 }) => isCorrectScope({ expect: "fileExport", scope: scope2 })
);
const fileRootRules = rules.filter(
({ scope: scope2 }) => isCorrectScope({ expect: "fileRoot", scope: scope2 })
);
const nestedSelectorsRules = rules.filter(
({ scope: scope2 }) => isCorrectScope({ expect: "nestedSelectors", scope: scope2 })
);
const filenamePath = import_path5.default.relative(
getProjectRoot({ cwd, projectRootConfig: config.projectRoot }),
filename
);
const regexParameters = config.regexParameters;
const selectorType = SELECTORS[nodeType];
const { isExportName, currentName, currentNode } = isExportedName({
nodeType,
node,
name
});
if (fileExportRules.length && isExportName) {
return validateRules({
rules: fileExportRules,
name: currentName,
selectorType,
node: currentNode,
nodeNotExported: node,
context,
filenamePath,
errorMessageId: "prohibitedSelectorExport",
regexParameters,
expressionName,
allowOnlySpecifiedSelectors,
scope: "fileExport",
allRules: rules
});
}
const isFileRootName = isNameFromFileRoot({
nodeType,
node
});
if (fileRootRules.length && isFileRootName && !isExportName) {
return validateRules({
rules: fileRootRules,
name,
selectorType,
node,
context,
filenamePath,
errorMessageId: "prohibitedSelectorRoot",
regexParameters,
expressionName,
allowOnlySpecifiedSelectors,
scope: "fileRoot",
allRules: rules
});
}
if (nestedSelectorsRules.length && !isExportName && !isFileRootName) {
return validateRules({
rules: nestedSelectorsRules,
name,
node,
filenamePath,
errorMessageId: "prohibitedSelectorNested",
regexParameters,
expressionName,
allowOnlySpecifiedSelectors,
scope: "nestedSelectors",
context,
allRules: rules,
selectorType
});
}
const { scope, errorMessageId } = getCurrentScopeData({
isFileExport: isExportName,
isFileRoot: isFileRootName
});
isSelectorAllowed({
rules: [],
scope,
allowOnlySpecifiedSelectors,
node,
selectorType,
report: context.report,
errorMessageId,
expressionName
});
};
// src/rules/fileComposition/helpers/handleClassDeclaration.ts
var handleClassDeclaration = ({
node,
context,
config,
fileConfig
}) => {
if (!node.id?.name) return;
validateFile({
node,
context,
name: node.id.name,
nodeType: "ClassDeclaration",
config,
fileConfig
});
};
// src/rules/fileComposition/helpers/handleFunctionDeclaration.ts
var handleFunctionDeclaration = ({
node,
context,
config,
fileConfig
}) => {
if (!node.id?.name) return;
validateFile({
node,
context,
name: node.id.name,
nodeType: "FunctionDeclaration",
config,
fileConfig
});
};
// src/rules/fileComposition/helpers/handleMethodDefinition.ts
var import_utils9 = require("@typescript-eslint/utils");
var handleMethodDefinition = ({
context,
node,
config,
fileConfig
}) => {
if (node.key.type !== import_utils9.TSESTree.AST_NODE_TYPES.Identifier) return;
validateFile({
node,
context,
name: node.key.name,
nodeType: "FunctionDeclaration",
config,
fileConfig
});
};
// src/rules/fileComposition/helpers/handlePropertyDefinition.ts
var import_utils10 = require("@typescript-eslint/utils");
var handlePropertyDefinition = ({
context,
node,
config,
fileConfig
}) => {
if (node.key.type !== import_utils10.TSESTree.AST_NODE_TYPES.Identifier) return;
const nodeType = node.value?.type === import_utils10.TSESTree.AST_NODE_TYPES.ArrowFunctionExpression ? "ArrowFunctionExpression" : "PropertyDefinition";
validateFile({
node,
context,
name: node.key.name,
nodeType,
config,
fileConfig
});
};
// src/rules/fileComposition/helpers/handleVariableDeclarator.ts
var import_utils11 = require("@typescript-eslint/utils");
var handleVariableDeclarator = ({
node,
context,
config,
fileConfig
}) => {
const expressionName = getIdentifierFromExpression(node.init);
if (node.id.type === import_utils11.TSESTree.AST_NODE_TYPES.ArrayPattern || node.id.type === import_utils11.TSESTree.AST_NODE_TYPES.ObjectPattern) {
if (expressionName)
return validateFile({
node,
context,
name: "*",
nodeType: "Expression",
expressionName,
config,
fileConfig
});
return validateFile({
node,
context,
name: "*",
nodeType: "VariableDeclarator",
config,
fileConfig
});
}
if (expressionName) {
return validateFile({
node,
context,
name: node.id.name,
nodeType: "Expression",
expressionName,
config,
fileConfig
});
}
const currentNodeType = node.init?.type === import_utils11.TSESTree.AST_NODE_TYPES.ArrowFunctionExpression ? "ArrowFunctionExpression" : "VariableDeclarator";
validateFile({
node,
context,
name: node.id.name,
nodeType: currentNodeType,
config,
fileConfig
});
};
// src/rules/fileComposition/helpers/validateRootSelectorsL