eslint-plugin-roblox-ts-x
Version:
A collection of ESLint rules specifically targeted for roblox-ts.
1,549 lines (1,523 loc) • 54.9 kB
JavaScript
import { getConstrainedTypeAtLocation, isBuiltinSymbolLike, isTypeFlagSet } from "@typescript-eslint/type-utils";
import { AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree } from "@typescript-eslint/utils";
import { RuleCreator, getParserServices } from "@typescript-eslint/utils/eslint-utils";
import ts9, { TypeFlags, isArrayLiteralExpression, isObjectLiteralExpression, isPropertyAccessExpression } from "typescript";
//#region package.json
var name = "eslint-plugin-roblox-ts-x";
var version = "2.1.0";
//#endregion
//#region src/util.ts
const createEslintRule = RuleCreator((name$1) => {
return `https://github.com/christopher-buss/eslint-plugin-roblox-ts-x/tree/main/src/rules/${name$1}/documentation.md`;
});
function assert(condition, message) {
if (!condition) throw new Error(message);
}
//#endregion
//#region src/utils/types.ts
const robloxTypes = new Set([
"CFrame",
"UDim",
"UDim2",
"Vector2",
"Vector2int16",
"Vector3",
"Vector3int16"
]);
function getRobloxDataTypeName(type) {
const symbol = type.getSymbol();
if (!symbol) return void 0;
const name$1 = symbol.getName();
return robloxTypes.has(name$1) ? name$1 : void 0;
}
function getRobloxDataTypeNameRecursive(type) {
let foundType;
isTypeRecursive(type, (innerType) => {
const directResult = getRobloxDataTypeName(innerType);
if (directResult === void 0) return false;
foundType = directResult;
return true;
});
return foundType;
}
function isArrayType(checker, type) {
return isTypeRecursive(type, (inner) => checker.isArrayLikeType(inner) && !isUnconstrainedType(inner));
}
function isDefinedType(type) {
return type.flags === TypeFlags.Object && type.getProperties().length === 0 && type.getCallSignatures().length === 0 && type.getConstructSignatures().length === 0 && type.getNumberIndexType() === void 0 && type.getStringIndexType() === void 0;
}
function isEmptyStringType(type) {
if (type.isStringLiteral()) return type.value === "";
return isStringType(type);
}
function isFunction(type) {
return isTypeFlagSet(type, TypeFlags.Object) && type.getCallSignatures().length > 0;
}
function isIterableFunctionType(program, type) {
return isBuiltinSymbolLike(program, type, ["IterableFunction"]);
}
function isMapType(program, type) {
return isBuiltinSymbolLike(program, type, [
"Map",
"ReadonlyMap",
"WeakMap"
]);
}
function isNumberLiteralType(type, value) {
if (type.isNumberLiteral()) return type.value === value;
return isNumberType(type);
}
function isNumberType(type) {
return isTypeFlagSet(type, TypeFlags.NumberLike);
}
function isPossiblyType(type, callback) {
const constrainedType = type.getConstraint() ?? type;
return isTypeRecursive(constrainedType, (innerType) => {
return isUnconstrainedType(innerType) || isDefinedType(innerType) || callback(innerType);
});
}
function isSetType(program, type) {
return isBuiltinSymbolLike(program, type, [
"Set",
"ReadonlySet",
"WeakSet"
]);
}
function isStringType(type) {
return isTypeFlagSet(type, TypeFlags.StringLike);
}
function isUnconstrainedType(type) {
return isTypeFlagSet(type, TypeFlags.Any | TypeFlags.Unknown | TypeFlags.TypeVariable);
}
function isTypeRecursive(type, predicate) {
if (type.isUnionOrIntersection()) return type.types.some((inner) => isTypeRecursive(inner, predicate));
return predicate(type);
}
//#endregion
//#region src/rules/lua-truthiness/rule.ts
const RULE_NAME$22 = "lua-truthiness";
const FALSY_STRING_NUMBER_CHECK = "falsy-string-number-check";
const messages$22 = { [FALSY_STRING_NUMBER_CHECK]: "0, NaN, and \"\" are falsy in TS. If intentional, disable this rule by placing `\"roblox-ts-x/lua-truthiness\": \"off\"` in your eslint.config file in the \"rules\" object." };
function checkTruthy(context, parserServices, node) {
const type = getConstrainedTypeAtLocation(parserServices, node);
const isAssignableToZero = isPossiblyType(type, (inner) => isNumberLiteralType(inner, 0));
const isAssignableToEmptyString = isPossiblyType(type, (inner) => isEmptyStringType(inner));
if (isAssignableToZero || isAssignableToEmptyString) context.report({
fix: void 0,
messageId: FALSY_STRING_NUMBER_CHECK,
node
});
}
function create$22(context) {
const parserServices = getParserServices(context);
function containsBoolean() {
return ({ test }) => {
if (test && test.type !== TSESTree.AST_NODE_TYPES.LogicalExpression) checkTruthy(context, parserServices, test);
};
}
return {
"ConditionalExpression": containsBoolean(),
"DoWhileStatement": containsBoolean(),
"ForStatement": containsBoolean(),
"IfStatement": containsBoolean(),
"LogicalExpression": ({ left, operator, parent, right }) => {
if (operator !== "??") {
checkTruthy(context, parserServices, left);
return;
}
if (parent.type === TSESTree.AST_NODE_TYPES.IfStatement) checkTruthy(context, parserServices, right);
},
"UnaryExpression[operator=\"!\"]": ({ argument }) => {
checkTruthy(context, parserServices, argument);
},
"WhileStatement": containsBoolean()
};
}
const luaTruthiness = createEslintRule({
create: create$22,
defaultOptions: [],
meta: {
docs: {
description: "Enforces the use of lua truthiness",
recommended: true,
requiresTypeChecking: true
},
messages: messages$22,
schema: [],
type: "problem"
},
name: RULE_NAME$22
});
//#endregion
//#region node_modules/.pnpm/ts-api-utils@2.1.0_typescript@5.8.3/node_modules/ts-api-utils/lib/index.js
function isFlagSet(allFlags, flag) {
return (allFlags & flag) !== 0;
}
function isFlagSetOnObject(obj, flag) {
return isFlagSet(obj.flags, flag);
}
function isObjectFlagSet(objectType, flag) {
return isFlagSet(objectType.objectFlags, flag);
}
var isTypeFlagSet$1 = isFlagSetOnObject;
var [tsMajor, tsMinor] = ts9.versionMajorMinor.split(".").map((raw) => Number.parseInt(raw, 10));
function isArrayBindingOrAssignmentPattern(node) {
return ts9.isArrayBindingPattern(node) || ts9.isArrayLiteralExpression(node);
}
var IntrinsicTypeFlags = ts9.TypeFlags.Intrinsic ?? ts9.TypeFlags.Any | ts9.TypeFlags.Unknown | ts9.TypeFlags.String | ts9.TypeFlags.Number | ts9.TypeFlags.BigInt | ts9.TypeFlags.Boolean | ts9.TypeFlags.BooleanLiteral | ts9.TypeFlags.ESSymbol | ts9.TypeFlags.Void | ts9.TypeFlags.Undefined | ts9.TypeFlags.Null | ts9.TypeFlags.Never | ts9.TypeFlags.NonPrimitive;
function isObjectType(type) {
return isTypeFlagSet$1(type, ts9.TypeFlags.Object);
}
function isTypeReference(type) {
return isObjectType(type) && isObjectFlagSet(type, ts9.ObjectFlags.Reference);
}
//#endregion
//#region src/rules/misleading-lua-tuple-checks/rule.ts
const RULE_NAME$21 = "misleading-lua-tuple-checks";
const BANNED_LUA_TUPLE_CHECK = "misleading-lua-tuple-check";
const LUA_TUPLE_DECLARATION = "lua-tuple-declaration";
const messages$21 = {
[BANNED_LUA_TUPLE_CHECK]: "Unexpected LuaTuple in conditional expression. Add [0].",
[LUA_TUPLE_DECLARATION]: "Unexpected LuaTuple in declaration, use array destructuring."
};
function checkLuaTupleUsage(context, parserServices, node) {
if (isLuaTuple(parserServices, node)) context.report({
fix: (fixer) => fixer.insertTextAfter(node, "[0]"),
messageId: BANNED_LUA_TUPLE_CHECK,
node
});
}
function create$21(context) {
const parserServices = getParserServices(context);
function containsBoolean({ test }) {
if (test && test.type !== AST_NODE_TYPES.LogicalExpression) checkLuaTupleUsage(context, parserServices, test);
}
return {
"AssignmentExpression[operator=\"=\"][left.type=\"Identifier\"]": (node) => {
validateAssignmentExpression(context, parserServices, node);
},
"ConditionalExpression, DoWhileStatement, IfStatement, ForStatement, WhileStatement": containsBoolean,
"ForOfStatement": (node) => {
validateForOfStatement(context, parserServices, node);
},
"LogicalExpression": ({ left, right }) => {
checkLuaTupleUsage(context, parserServices, left);
checkLuaTupleUsage(context, parserServices, right);
},
"UnaryExpression[operator=\"!\"]": ({ argument }) => {
checkLuaTupleUsage(context, parserServices, argument);
},
"VariableDeclarator[id.type=\"Identifier\"]": (node) => {
validateVariableDeclarator(context, parserServices, node);
}
};
}
function ensureArrayDestructuring(context, parserServices, leftNode) {
const esNode = parserServices.esTreeNodeToTSNodeMap.get(leftNode);
if (isArrayBindingOrAssignmentPattern(esNode)) return;
const fixer = fixIntoArrayDestructuring(context, leftNode);
context.report({
fix: fixer,
messageId: LUA_TUPLE_DECLARATION,
node: leftNode
});
}
function fixIntoArrayDestructuring(context, node) {
const { sourceCode } = context;
return (fixer) => {
let replacement = `[${node.name}]`;
if (node.typeAnnotation) replacement += sourceCode.getText(node.typeAnnotation);
return fixer.replaceText(node, replacement);
};
}
function handleIterableFunction(context, parserServices, node, type) {
if (!isTypeReference(type)) return;
const checker = parserServices.program.getTypeChecker();
const aliasSymbol = checker.getTypeArguments(type)[0]?.aliasSymbol;
if (!aliasSymbol || aliasSymbol.escapedName.toString() !== "LuaTuple") return;
if (node.left.type === AST_NODE_TYPES.Identifier) {
ensureArrayDestructuring(context, parserServices, node.left);
return;
}
if (node.left.type !== AST_NODE_TYPES.VariableDeclaration) return;
const variableDeclarator = node.left.declarations[0];
if (variableDeclarator.id.type === AST_NODE_TYPES.Identifier) ensureArrayDestructuring(context, parserServices, variableDeclarator.id);
}
function isLuaTuple(parserServices, node) {
const { aliasSymbol } = getConstrainedTypeAtLocation(parserServices, node);
return (aliasSymbol && aliasSymbol.escapedName.toString() === "LuaTuple") ?? false;
}
function validateAssignmentExpression(context, parserServices, node) {
if (!isLuaTuple(parserServices, node.left) && isLuaTuple(parserServices, node.right)) ensureArrayDestructuring(context, parserServices, node.left);
}
function validateForOfStatement(context, parserServices, node) {
const rightNode = node.right;
const type = getConstrainedTypeAtLocation(parserServices, rightNode);
if (isIterableFunctionType(parserServices.program, type)) handleIterableFunction(context, parserServices, node, type);
else checkLuaTupleUsage(context, parserServices, rightNode);
}
function validateVariableDeclarator(context, parserServices, node) {
if (node.init && isLuaTuple(parserServices, node.init)) ensureArrayDestructuring(context, parserServices, node.id);
}
const misleadingLuaTupleChecks = createEslintRule({
create: create$21,
defaultOptions: [],
meta: {
docs: {
description: "Disallow the use of LuaTuple in conditional expressions",
recommended: true,
requiresTypeChecking: true
},
fixable: "code",
messages: messages$21,
schema: [],
type: "problem"
},
name: RULE_NAME$21
});
//#endregion
//#region src/rules/no-any/rule.ts
const RULE_NAME$20 = "no-any";
const ANY_VIOLATION = "any-violation";
const SUGGEST_UNKNOWN = "suggest-unknown";
const messages$20 = {
[ANY_VIOLATION]: "Type 'any' is not supported in roblox-ts.",
[SUGGEST_UNKNOWN]: "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."
};
function create$20(context, [{ fixToUnknown }]) {
return { TSAnyKeyword: (node) => {
const isKeyofAny = isNodeWithinKeyofAny(node);
if (isKeyofAny) return;
const fixOrSuggest = {
fix: fixToUnknown ? (fixer) => fixer.replaceText(node, "unknown") : null,
suggest: [{
fix: (fixer) => fixer.replaceText(node, "unknown"),
messageId: SUGGEST_UNKNOWN
}]
};
context.report({
messageId: ANY_VIOLATION,
node,
...fixOrSuggest
});
} };
}
function isNodeWithinKeyofAny(node) {
return node.parent.type === AST_NODE_TYPES.TSTypeOperator && node.parent.operator === "keyof";
}
const noAny = createEslintRule({
create: create$20,
defaultOptions: [{ fixToUnknown: true }],
meta: {
defaultOptions: [{ fixToUnknown: true }],
docs: {
description: "Disallow values of type `any`. Use `unknown` instead",
recommended: true,
requiresTypeChecking: false
},
fixable: "code",
hasSuggestions: true,
messages: messages$20,
schema: [{
additionalProperties: false,
properties: { fixToUnknown: {
description: "Whether to enable auto-fixing in which the `any` type is converted to the `unknown` type.'",
type: "boolean"
} },
type: "object"
}],
type: "problem"
},
name: RULE_NAME$20
});
//#endregion
//#region src/rules/no-array-pairs/rule.ts
function makeViolationText(name$1) {
return `Do not use Array<T> with ${name$1}(). Key values will not be shifted from 1-indexed to 0-indexed.`;
}
const RULE_NAME$19 = "no-array-pairs";
const ARRAY_PAIRS_VIOLATION = "array-pairs-violation";
const ARRAY_IPAIRS_VIOLATION = "array-ipairs-violation";
const messages$19 = {
[ARRAY_IPAIRS_VIOLATION]: makeViolationText("ipairs"),
[ARRAY_PAIRS_VIOLATION]: makeViolationText("pairs")
};
function create$19(context) {
const parserServices = getParserServices(context);
const checker = parserServices.program.getTypeChecker();
return { "CallExpression[callee.name=\"ipairs\"], CallExpression[callee.name=\"pairs\"]": (node) => {
if (node.callee.type !== AST_NODE_TYPES.Identifier || !node.arguments[0]) return;
const type = getConstrainedTypeAtLocation(parserServices, node.arguments[0]);
if (!isArrayType(checker, type)) return;
context.report({
messageId: node.callee.name === "pairs" ? ARRAY_PAIRS_VIOLATION : ARRAY_IPAIRS_VIOLATION,
node
});
} };
}
const noArrayPairs = createEslintRule({
create: create$19,
defaultOptions: [],
meta: {
docs: {
description: "Disallow usage of pairs() and ipairs() with Array<T>",
recommended: true,
requiresTypeChecking: true
},
messages: messages$19,
schema: [],
type: "problem"
},
name: RULE_NAME$19
});
//#endregion
//#region src/rules/no-enum-merging/rule.ts
const RULE_NAME$18 = "no-enum-merging";
const ENUM_MERGING_VIOLATION = "enum-merging-violation";
const messages$18 = { [ENUM_MERGING_VIOLATION]: "Enum merging is not supported in roblox-ts. Declare all members in a single enum." };
function create$18(context) {
return { TSEnumDeclaration(node) {
const currentScope = context.sourceCode.getScope(node).upper;
if (currentScope === null) return;
const variable = currentScope.set.get(node.id.name);
if (variable === void 0) return;
if (variable.defs.length <= 1) return;
context.report({
messageId: ENUM_MERGING_VIOLATION,
node: node.id
});
} };
}
const noEnumMerging = createEslintRule({
create: create$18,
defaultOptions: [],
meta: {
docs: {
description: "Disallow merging enum declarations",
recommended: true,
requiresTypeChecking: false
},
messages: messages$18,
schema: [],
type: "problem"
},
name: RULE_NAME$18
});
//#endregion
//#region src/rules/no-export-assignment-let/rule.ts
const RULE_NAME$17 = "no-export-assignment-let";
const EXPORT_VIOLATION = "export-violation";
const messages$17 = { [EXPORT_VIOLATION]: "Cannot use `export =` on a `let` variable!" };
function create$17(context) {
return { TSExportAssignment(node) {
const { expression } = node;
if (expression.type !== AST_NODE_TYPES.Identifier) return;
const variable = context.sourceCode.getScope(node).set.get(expression.name);
if (variable === void 0) return;
const parent = variable.defs[0]?.parent;
if (parent && parent.type === AST_NODE_TYPES.VariableDeclaration && parent.kind === "let") context.report({
messageId: EXPORT_VIOLATION,
node
});
} };
}
const noExportAssignableLet = createEslintRule({
create: create$17,
defaultOptions: [],
meta: {
docs: {
description: "Disallow using `export =` on a let variable",
recommended: true,
requiresTypeChecking: false
},
messages: messages$17,
schema: [],
type: "problem"
},
name: RULE_NAME$17
});
//#endregion
//#region src/rules/no-for-in/rule.ts
const RULE_NAME$16 = "no-for-in";
const FOR_IN_VIOLATION = "for-in-violation";
const messages$16 = { [FOR_IN_VIOLATION]: "For-in loops are forbidden because it always types the iterator variable as `string`. Use for-of or array.forEach instead." };
function create$16(context) {
return { ForInStatement(node) {
context.report({
fix: (fix) => fix.replaceTextRange([node.left.range[1], node.right.range[0]], " of "),
messageId: FOR_IN_VIOLATION,
node
});
} };
}
const noForIn = createEslintRule({
create: create$16,
defaultOptions: [],
meta: {
docs: {
description: "Disallow iterating with a for-in loop",
recommended: true,
requiresTypeChecking: false
},
fixable: "code",
messages: messages$16,
schema: [],
type: "problem"
},
name: RULE_NAME$16
});
//#endregion
//#region src/rules/no-function-expression-name/rule.ts
const RULE_NAME$15 = "no-function-expression-name";
const FUNCTION_EXPRESSION_VIOLATION = "function-expression-violation";
const messages$15 = { [FUNCTION_EXPRESSION_VIOLATION]: "Function expression names are not supported!" };
function create$15(context) {
return { FunctionExpression(node) {
const { id } = node;
if (id === null) return;
const variable = context.sourceCode.getScope(node).set.get(id.name);
const referenced = variable?.references.some((ref) => ref.identifier !== id) ?? false;
context.report({
fix: referenced ? null : (fixer) => fixer.removeRange([id.range[0] - 1, id.range[1]]),
messageId: FUNCTION_EXPRESSION_VIOLATION,
node: id
});
} };
}
const noFunctionExpressionName = createEslintRule({
create: create$15,
defaultOptions: [],
meta: {
docs: {
description: "Disallow the use of function expression names",
recommended: true,
requiresTypeChecking: false
},
fixable: "code",
messages: messages$15,
schema: [],
type: "problem"
},
name: RULE_NAME$15
});
//#endregion
//#region src/rules/no-get-set/rule.ts
const RULE_NAME$14 = "no-get-set";
const GET_SET_VIOLATION = "get-set-violation";
const messages$14 = { [GET_SET_VIOLATION]: "Getters and Setters are not supported for performance reasons. Please use a normal method instead." };
function create$14(context) {
function checkMethodDefinition(nodes) {
for (const node of nodes) if ((node.type === AST_NODE_TYPES.MethodDefinition || node.type === AST_NODE_TYPES.Property) && (node.kind === "get" || node.kind === "set")) context.report({
fix: (fixer) => fixer.removeRange([node.key.range[0] - 1, node.key.range[0]]),
messageId: GET_SET_VIOLATION,
node
});
}
return {
ClassBody: (node) => {
checkMethodDefinition(node.body);
},
ObjectExpression: (node) => {
checkMethodDefinition(node.properties);
}
};
}
const noGetSet = createEslintRule({
create: create$14,
defaultOptions: [],
meta: {
docs: {
description: "Disallow getters and setters",
recommended: true,
requiresTypeChecking: false
},
fixable: "code",
messages: messages$14,
schema: [],
type: "problem"
},
name: RULE_NAME$14
});
//#endregion
//#region src/rules/no-implicit-self/rule.ts
const RULE_NAME$13 = "no-implicit-self";
const COLON_VIOLATION = "violation";
const messages$13 = { [COLON_VIOLATION]: "Enforce the use of `.` instead of `:` for method calls" };
function create$13(context) {
return { ["LabeledStatement[body.type='ExpressionStatement'][body.expression.type='CallExpression'],LabeledStatement[body.expression.type='MemberExpression']"]: (node) => {
const { sourceCode } = context;
const { body, label } = node;
const bodyText = sourceCode.getText(body);
const labelText = sourceCode.getText(label);
const between = sourceCode.text.slice(label.range[1], body.range[0]);
if (/^:\s/g.test(between)) return;
const fixedText = `${labelText}.${bodyText}`;
context.report({
fix(fixer) {
return fixer.replaceText(node, fixedText);
},
messageId: COLON_VIOLATION,
node
});
} };
}
const noImplicitSelf = createEslintRule({
create: create$13,
defaultOptions: [],
meta: {
docs: {
description: "Enforce the use of `.` instead of `:` for method calls",
recommended: false,
requiresTypeChecking: false
},
fixable: "code",
hasSuggestions: false,
messages: messages$13,
schema: [],
type: "problem"
},
name: RULE_NAME$13
});
//#endregion
//#region src/rules/no-invalid-identifier/rule.ts
const RULE_NAME$12 = "no-invalid-identifier";
const BANNED_KEYWORDS = new Set([
"and",
"elseif",
"end",
"error",
"local",
"nil",
"not",
"or",
"repeat",
"then",
"until"
]);
const LUAU_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
const INVALID_CHARACTERS = "invalid-characters";
const INVALID_IDENTIFIER = "invalid-identifier";
const messages$12 = {
[INVALID_CHARACTERS]: "Identifier '{{ identifier }}' contains invalid characters. Only letters, digits, and underscores are allowed.",
[INVALID_IDENTIFIER]: "Avoid using '{{ identifier }}' as an identifier, as it is a reserved keyword in Luau."
};
function create$12(context) {
const { sourceCode } = context;
return {
[[
"VariableDeclaration",
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
"CatchClause",
"TSEnumDeclaration",
"TSModuleDeclaration"
].join(",")](node) {
for (const variable of sourceCode.getDeclaredVariables(node)) validateIdentifier(context, node, variable.name);
},
"ClassDeclaration, ClassExpression"(node) {
if (node.id?.name !== void 0) validateIdentifier(context, node, node.id.name);
},
"ImportDeclaration"(node) {
for (const variable of sourceCode.getDeclaredVariables(node)) validateIdentifier(context, node, variable.name, () => isImportAlias(node));
}
};
}
function isImportAlias(node) {
for (const specifier of node.specifiers) if (specifier.type === AST_NODE_TYPES.ImportSpecifier && (specifier.imported.type !== AST_NODE_TYPES.Identifier || specifier.local.name !== specifier.imported.name)) return true;
return false;
}
function isRestricted(name$1) {
return BANNED_KEYWORDS.has(name$1) || !LUAU_IDENTIFIER_REGEX.test(name$1);
}
function validateIdentifier(context, node, name$1, validate) {
if (!isRestricted(name$1) || validate?.() === false) return;
context.report({
data: { identifier: name$1 },
messageId: BANNED_KEYWORDS.has(name$1) ? INVALID_IDENTIFIER : INVALID_CHARACTERS,
node
});
}
const noInvalidIdentifier = createEslintRule({
create: create$12,
defaultOptions: [],
meta: {
docs: {
description: "Disallow the use of Luau reserved keywords as identifiers",
recommended: true,
requiresTypeChecking: false
},
messages: messages$12,
schema: [],
type: "problem"
},
name: RULE_NAME$12
});
//#endregion
//#region src/rules/no-namespace-merging/rule.ts
const RULE_NAME$11 = "no-namespace-merging";
const NAMESPACE_MERGING_VIOLATION = "namespace-merging-violation";
const messages$11 = { [NAMESPACE_MERGING_VIOLATION]: "Namespace merging is not supported in roblox-ts. Declare all members in a single namespace." };
function checkNamespaceMerging(context, node) {
if (shouldSkipNode(node)) return;
const variable = getNamespaceVariable(context, node);
if (variable === void 0) return;
const allTypeOnly = variable.defs.every((definition) => {
return definition.node.type === AST_NODE_TYPES.TSModuleDeclaration && isTypeOnlyNamespace(definition.node);
});
if (!allTypeOnly) context.report({
messageId: NAMESPACE_MERGING_VIOLATION,
node: node.id
});
}
function create$11(context) {
return { "TSModuleDeclaration[global!=true][id.type!='Literal']"(node) {
checkNamespaceMerging(context, node);
} };
}
function getNamespaceVariable(context, node) {
const currentScope = context.sourceCode.getScope(node).upper;
if (currentScope === null) return void 0;
const variable = currentScope.set.get(node.id.name);
if (variable === void 0 || variable.defs.length <= 1) return void 0;
return variable;
}
function isTypeOnlyNamespace(node) {
if (!node.body) return true;
return node.body.body.every((statement) => {
if (statement.type === AST_NODE_TYPES.ExportNamedDeclaration) {
if (statement.declaration) return statement.declaration.type === AST_NODE_TYPES.TSTypeAliasDeclaration || statement.declaration.type === AST_NODE_TYPES.TSInterfaceDeclaration;
return false;
}
return statement.type === AST_NODE_TYPES.TSTypeAliasDeclaration || statement.type === AST_NODE_TYPES.TSInterfaceDeclaration || statement.type === AST_NODE_TYPES.TSModuleDeclaration;
});
}
function shouldSkipNode(node) {
return node.parent.type === AST_NODE_TYPES.TSModuleDeclaration || node.id.type !== AST_NODE_TYPES.Identifier;
}
const noNamespaceMerging = createEslintRule({
create: create$11,
defaultOptions: [],
meta: {
docs: {
description: "Disallow merging namespace declarations",
recommended: true,
requiresTypeChecking: false
},
messages: messages$11,
schema: [],
type: "problem"
},
name: RULE_NAME$11
});
//#endregion
//#region src/rules/no-null/rule.ts
const RULE_NAME$10 = "no-null";
const NULL_VIOLATION = "null-violation";
const messages$10 = { [NULL_VIOLATION]: "Usage of 'null' is not allowed. Use 'undefined' instead." };
function create$10(context) {
return {
Literal(node) {
if (node.value === null) replaceNull(context, node);
},
TSNullKeyword(node) {
replaceNull(context, node);
}
};
}
function replaceNull(context, node) {
context.report({
fix: (fixer) => fixer.replaceText(node, "undefined"),
messageId: NULL_VIOLATION,
node
});
}
const noNull = createEslintRule({
create: create$10,
defaultOptions: [],
meta: {
docs: {
description: "Disallow usage of the `null` keyword",
recommended: true,
requiresTypeChecking: false
},
fixable: "code",
messages: messages$10,
schema: [],
type: "problem"
},
name: RULE_NAME$10
});
//#endregion
//#region src/rules/no-object-math/rule.ts
const RULE_NAME$9 = "no-object-math";
const OBJECT_MATH_VIOLATION = "object-math-violation";
const OTHER_VIOLATION = "other-violation";
const messages$9 = {
[OBJECT_MATH_VIOLATION]: "'{{operator}}' is not supported for Roblox DataType math operations. Use .{{method}}() instead.",
[OTHER_VIOLATION]: "Cannot use {{operator}} on this Roblox Datatype."
};
const operationConstraints = new Map([
["CFrame", new Map([
["add", {
acceptedTypes: ["Vector3"],
allowSwapped: false
}],
["mul", {
acceptedTypes: ["CFrame", "Vector3"],
allowSwapped: false
}],
["sub", {
acceptedTypes: ["Vector3"],
allowSwapped: false
}]
])],
["UDim2", new Map([["add", {
acceptedTypes: "same",
allowSwapped: false
}], ["sub", {
acceptedTypes: "same",
allowSwapped: false
}]])],
["UDim", new Map([["add", {
acceptedTypes: "same",
allowSwapped: false
}], ["sub", {
acceptedTypes: "same",
allowSwapped: false
}]])],
["Vector2", new Map([
["add", {
acceptedTypes: "same",
allowSwapped: false
}],
["div", {
acceptedTypes: ["Vector2", "number"],
allowSwapped: false
}],
["mul", {
acceptedTypes: ["Vector2", "number"],
allowSwapped: true
}],
["sub", {
acceptedTypes: "same",
allowSwapped: false
}]
])],
["Vector2int16", new Map([
["add", {
acceptedTypes: "same",
allowSwapped: false
}],
["div", {
acceptedTypes: "same",
allowSwapped: false
}],
["mul", {
acceptedTypes: "same",
allowSwapped: false
}],
["sub", {
acceptedTypes: "same",
allowSwapped: false
}]
])],
["Vector3", new Map([
["add", {
acceptedTypes: "same",
allowSwapped: false
}],
["div", {
acceptedTypes: ["Vector3", "number"],
allowSwapped: false
}],
["mul", {
acceptedTypes: ["Vector3", "number"],
allowSwapped: true
}],
["sub", {
acceptedTypes: "same",
allowSwapped: false
}]
])],
["Vector3int16", new Map([
["add", {
acceptedTypes: "same",
allowSwapped: false
}],
["div", {
acceptedTypes: "same",
allowSwapped: false
}],
["mul", {
acceptedTypes: "same",
allowSwapped: false
}],
["sub", {
acceptedTypes: "same",
allowSwapped: false
}]
])]
]);
const unaryOperationSupport = new Map([
["CFrame", false],
["UDim2", true],
["UDim", true],
["Vector2", true],
["Vector2int16", true],
["Vector3", true],
["Vector3int16", true]
]);
const mathOperationToMacroName = new Map([
["*", "mul"],
["+", "add"],
["-", "sub"],
["/", "div"]
]);
const safeOperationSymbols = new Set(["!==", "==="]);
function buildMethodCallFix({ fixer, left, macroName, operator, right, sourceCode }) {
const textBetween = sourceCode.text.slice(left.range[1], right.range[0]);
const { afterOp, beforeOp, hasParentheses } = extractOperatorContext(textBetween, operator);
if (hasParentheses) return [fixer.replaceTextRange([left.range[1] + beforeOp.length, right.range[0] - afterOp.length], `.${macroName}(`), fixer.insertTextAfter(right, ")")];
return [fixer.replaceTextRange([left.range[1], right.range[0]], `.${macroName}(`), fixer.insertTextAfter(right, ")")];
}
function buildSwappedFix({ fixer, left, macroName, right, sourceCode }) {
return [fixer.replaceTextRange([left.range[0], right.range[1]], `${sourceCode.getText(right)}.${macroName}(${sourceCode.getText(left)})`)];
}
function buildUnaryFix(fixer, sourceCode, node) {
const argumentText = sourceCode.getText(node.argument);
return [fixer.replaceText(node, `${argumentText}.mul(-1)`)];
}
function checkConstraints(parameters) {
const { constraints, otherNode, otherType, thisType } = parameters;
return isSameTypeConstraint(constraints, otherType, thisType) || isNumberConstraint(constraints, otherType, otherNode) || isTypeInList(constraints, otherType, otherNode);
}
function create$9(context) {
const parserServices = getParserServices(context);
return {
"BinaryExpression": (node) => {
handleBinaryExpression(node, context, parserServices);
},
"UnaryExpression[operator=\"-\"]": (node) => {
handleUnaryExpression(node, context, parserServices);
}
};
}
function createOperatorFix(fixContext) {
const { context, fixer, macroName, node, shouldSwap = false } = fixContext;
const { sourceCode } = context;
if (node.type === AST_NODE_TYPES.UnaryExpression) return buildUnaryFix(fixer, sourceCode, node);
const { left, operator, right } = node;
if (shouldSwap) return buildSwappedFix({
fixer,
left,
macroName,
right,
sourceCode
});
return buildMethodCallFix({
fixer,
left,
macroName,
operator,
right,
sourceCode
});
}
function createValidationResult(dataType, isValid, shouldSwap = false) {
return {
dataType,
isValid,
shouldSwap
};
}
function createViolationContext({ context, macroName, node, operator, validation }) {
const violationType = validation.isValid ? "math-operation" : "unsupported";
const violationContext = {
context,
node,
operator,
type: violationType
};
if (validation.isValid) {
violationContext.macroName = macroName;
violationContext.operator = operator;
violationContext.shouldSwap = validation.shouldSwap;
}
return violationContext;
}
function extractOperatorContext(textBetween, operator) {
const hasParentheses = textBetween.includes(")") && textBetween.includes(operator);
if (!hasParentheses) return {
afterOp: "",
beforeOp: "",
hasParentheses: false
};
const operatorIndex = textBetween.indexOf(operator);
const beforeOp = textBetween.slice(0, operatorIndex).trimEnd();
const afterOp = textBetween.slice(operatorIndex + operator.length).trimStart();
return {
afterOp,
beforeOp,
hasParentheses: true
};
}
function getOperationConstraints(operandType, macroName) {
return operationConstraints.get(operandType)?.get(macroName);
}
function getRobloxTypeFromBinaryExpr(node, parserServices) {
const { left, operator, right } = node;
const macroName = mathOperationToMacroName.get(operator);
if (macroName === void 0) return void 0;
const leftType = getRobloxTypeName(left, parserServices);
const rightType = getRobloxTypeName(right, parserServices);
const validation = validateOperation({
leftNode: left,
leftType,
operator,
rightNode: right,
rightType
});
return validation.isValid ? validation.dataType : void 0;
}
function getRobloxTypeFromMethodCall(node, parserServices) {
if (node.callee.type !== AST_NODE_TYPES.MemberExpression || node.callee.property.type !== AST_NODE_TYPES.Identifier) return void 0;
const methodName = node.callee.property.name;
const objectType = getSimpleRobloxType(node.callee.object, parserServices);
if (objectType !== void 0 && operationConstraints.get(objectType)?.has(methodName) === true) return objectType;
return void 0;
}
function getRobloxTypeName(node, parserServices) {
const simpleType = getSimpleRobloxType(node, parserServices);
if (simpleType !== void 0) return simpleType;
if (node.type === AST_NODE_TYPES.CallExpression) return getRobloxTypeFromMethodCall(node, parserServices);
if (node.type === AST_NODE_TYPES.BinaryExpression) return getRobloxTypeFromBinaryExpr(node, parserServices);
return void 0;
}
function getSimpleRobloxType(node, parserServices) {
const type = getConstrainedTypeAtLocation(parserServices, node);
return getRobloxDataTypeNameRecursive(type);
}
function handleBinaryExpression(node, context, parserServices) {
const { left, operator, right } = node;
if (shouldSkipOperation(operator)) return;
const leftDataType = getRobloxTypeName(left, parserServices);
const rightDataType = getRobloxTypeName(right, parserServices);
if (leftDataType === void 0 && rightDataType === void 0) return;
processMathOperation({
context,
leftDataType,
node,
operands: {
left,
operator,
right
},
rightDataType
});
}
function handleUnaryExpression(node, context, parserServices) {
const argumentDataType = getRobloxTypeName(node.argument, parserServices);
if (argumentDataType === void 0) return;
const violationType = unaryOperationSupport.get(argumentDataType) === true ? "unary-operation" : "unsupported";
reportViolation({
context,
node,
operator: node.operator,
type: violationType
});
}
function isNumberConstraint(constraints, otherType, otherNode) {
return constraints.acceptedTypes === "number" && (otherType === void 0 || isNumericLiteral(otherNode));
}
function isNumericLiteral(node) {
return node.type === AST_NODE_TYPES.Literal && typeof node.value === "number";
}
function isSameTypeConstraint(constraints, otherType, thisType) {
return constraints.acceptedTypes === "same" && otherType === thisType;
}
function isTypeInList(constraints, otherType, otherNode) {
if (!Array.isArray(constraints.acceptedTypes)) return false;
if (constraints.acceptedTypes.includes("number") && (otherType === void 0 || isNumericLiteral(otherNode))) return true;
return otherType !== void 0 && constraints.acceptedTypes.includes(otherType);
}
function processMathOperation({ context, leftDataType, node, operands, rightDataType }) {
const { left, operator, right } = operands;
const macroName = mathOperationToMacroName.get(operator);
if (macroName === void 0) {
reportViolation({
context,
node,
operator,
type: "unsupported"
});
return;
}
const validation = validateOperation({
leftNode: left,
leftType: leftDataType,
operator,
rightNode: right,
rightType: rightDataType
});
const violationContext = createViolationContext({
context,
macroName,
node,
operator,
validation
});
reportViolation(violationContext);
}
function reportViolation(violationContext) {
const { context, macroName, node, operator, shouldSwap = false, type } = violationContext;
if (type === "unsupported") {
context.report({
messageId: OTHER_VIOLATION,
node
});
return;
}
const data = {
method: macroName ?? "mul",
operator
};
context.report({
data,
fix: (fixer) => {
return createOperatorFix({
context,
fixer,
macroName: macroName ?? "mul",
node,
shouldSwap
});
},
messageId: OBJECT_MATH_VIOLATION,
node
});
}
function shouldSkipOperation(operator) {
return safeOperationSymbols.has(operator);
}
function tryOperandValidation({ macroName, operandType, otherNode, otherType }) {
const constraints = getOperationConstraints(operandType, macroName);
if (!constraints) return void 0;
const isValid = checkConstraints({
constraints,
otherNode,
otherType,
thisType: operandType
});
return isValid ? createValidationResult(operandType, true, false) : void 0;
}
function trySwappedValidation({ macroName, operandType, otherNode, otherType }) {
const constraints = getOperationConstraints(operandType, macroName);
if (constraints?.allowSwapped !== true) return void 0;
const isValid = checkConstraints({
constraints,
otherNode,
otherType,
thisType: operandType
});
return isValid ? createValidationResult(operandType, true, true) : void 0;
}
function validateOperation(context) {
const { leftNode, leftType, operator, rightNode, rightType } = context;
const macroName = mathOperationToMacroName.get(operator);
if (macroName === void 0) return createValidationResult(void 0, false);
if (leftType !== void 0) {
const result = tryOperandValidation({
macroName,
operandType: leftType,
otherNode: rightNode,
otherType: rightType
});
if (result) return result;
}
if (rightType !== void 0) {
const result = trySwappedValidation({
macroName,
operandType: rightType,
otherNode: leftNode,
otherType: leftType
});
if (result) return result;
}
return createValidationResult(void 0, false);
}
const noObjectMath = createEslintRule({
create: create$9,
defaultOptions: [],
meta: {
docs: {
description: "Enforce DataType math methods over operators",
recommended: true,
requiresTypeChecking: true
},
fixable: "code",
hasSuggestions: false,
messages: messages$9,
schema: [],
type: "problem"
},
name: RULE_NAME$9
});
//#endregion
//#region src/rules/no-post-fix-new/rule.ts
const RULE_NAME$8 = "no-post-fix-new";
const NEW_VIOLATION = "new-violation";
const messages$8 = { [NEW_VIOLATION]: "Calling .new() on objects without a .new() method is probably a mistake. Use `new X()` instead." };
function create$8(context) {
const parserServices = getParserServices(context);
const checker = parserServices.program.getTypeChecker();
return { CallExpression(node) {
handleCallExpression(node, context, parserServices, checker);
} };
}
function handleCallExpression(node, context, parserServices, checker) {
const propertyAccess = parserServices.esTreeNodeToTSNodeMap.get(node.callee);
if (!isPropertyAccessExpression(propertyAccess) || propertyAccess.name.text !== "new") return;
const objectType = checker.getTypeAtLocation(propertyAccess.expression);
const hasNewProperty = objectType.getProperty("new");
const hasNewMethod = hasNewProperty && isFunction(checker.getTypeOfSymbolAtLocation(hasNewProperty, propertyAccess.expression));
if (!(hasNewMethod ?? false)) replaceWithNewExpression(context, node);
}
function replaceWithNewExpression(context, node) {
context.report({
fix: (fixer) => {
const { sourceCode } = context;
const accessNode = node.callee;
const exprText = sourceCode.getText(accessNode.object);
const argsText = sourceCode.getText().slice(accessNode.range[1], node.range[1]);
const shouldWrap = !(accessNode.object.type === AST_NODE_TYPES.Identifier || accessNode.object.type === AST_NODE_TYPES.MemberExpression && !accessNode.object.computed);
const replaced = shouldWrap ? `new (${exprText})${argsText}` : `new ${exprText}${argsText}`;
return [fixer.replaceText(node, replaced)];
},
messageId: NEW_VIOLATION,
node
});
}
const noPostFixNew = createEslintRule({
create: create$8,
defaultOptions: [],
meta: {
docs: {
description: "Disallow .new() on objects without a .new() method",
recommended: true,
requiresTypeChecking: true
},
fixable: "code",
messages: messages$8,
schema: [],
type: "problem"
},
name: RULE_NAME$8
});
//#endregion
//#region src/rules/no-preceding-spread-element/rule.ts
const RULE_NAME$7 = "no-preceding-spread-element";
const PRECEDING_SPREAD_VIOLATION = "preceding-rest-violation";
const messages$7 = { [PRECEDING_SPREAD_VIOLATION]: "Spread element must come last in a list of arguments!" };
function create$7(context) {
const parserServices = getParserServices(context);
return { SpreadElement(node) {
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
const { parent } = tsNode;
if (!isArrayLiteralExpression(parent) && !isObjectLiteralExpression(parent) && parent.arguments && parent.arguments[parent.arguments.length - 1] !== tsNode) context.report({
messageId: PRECEDING_SPREAD_VIOLATION,
node
});
} };
}
const noPrecedingSpreadElement = createEslintRule({
create: create$7,
defaultOptions: [],
meta: {
docs: {
description: "Disallow spread elements not last in a list of arguments",
recommended: true,
requiresTypeChecking: true
},
messages: messages$7,
schema: [],
type: "problem"
},
name: RULE_NAME$7
});
//#endregion
//#region src/rules/no-private-identifier/rule.ts
const RULE_NAME$6 = "no-private-identifier";
const PRIVATE_IDENTIFIER_VIOLATION = "private-identifier-violation";
const messages$6 = { [PRIVATE_IDENTIFIER_VIOLATION]: "Private identifiers (`#`) are not supported in roblox-ts. Use the 'private' access modifier instead." };
function create$6(context) {
return { PrivateIdentifier(node) {
context.report({
fix: (fixer) => fixer.replaceText(node, `private ${node.name}`),
messageId: PRIVATE_IDENTIFIER_VIOLATION,
node
});
} };
}
const noPrivateIdentifier = createEslintRule({
create: create$6,
defaultOptions: [],
meta: {
docs: {
description: "Disallow the use of private identifiers (`#`)",
recommended: true,
requiresTypeChecking: false
},
fixable: "code",
messages: messages$6,
schema: [],
type: "problem"
},
name: RULE_NAME$6
});
//#endregion
//#region src/rules/no-unsupported-syntax/rule.ts
const RULE_NAME$5 = "no-unsupported-syntax";
const GLOBAL_THIS_VIOLATION = "global-this-violation";
const LABEL_VIOLATION = "label-violation";
const PROTOTYPE_VIOLATION = "prototype-violation";
const REGEX_LITERAL_VIOLATION = "regex-literal-violation";
const SPREAD_DESTRUCTURING_VIOLATION = "spread-destructuring-violation";
const messages$5 = {
[GLOBAL_THIS_VIOLATION]: "`globalThis` is not supported in roblox-ts.",
[LABEL_VIOLATION]: "`label` is not supported in roblox-ts.",
[PROTOTYPE_VIOLATION]: "`.prototype` is not supported in roblox-ts.",
[REGEX_LITERAL_VIOLATION]: "Regex literals are not supported in roblox-ts",
[SPREAD_DESTRUCTURING_VIOLATION]: "Operator `...` is not supported for destructuring!"
};
function create$5(context) {
return {
ArrayPattern: (node) => {
reportInvalidSpreadDestructure(context, node);
},
Identifier: (node) => {
reportGlobalThisViolation(context, node);
},
LabeledStatement: (node) => {
reportInvalidLabeledStatement(context, node);
},
Literal: (node) => {
reportRegexViolation(context, node);
},
MemberExpression: (node) => {
reportPrototypeViolation(context, node);
},
ObjectPattern: (node) => {
reportInvalidSpreadDestructure(context, node);
}
};
}
function reportGlobalThisViolation(context, node) {
if (node.name === "globalThis") context.report({
messageId: GLOBAL_THIS_VIOLATION,
node
});
}
function reportInvalidLabeledStatement(context, node) {
context.report({
messageId: LABEL_VIOLATION,
node
});
}
function reportInvalidSpreadDestructure(context, node) {
const members = node.type === AST_NODE_TYPES.ArrayPattern ? node.elements : node.properties;
for (const member of members) if (member?.type === AST_NODE_TYPES.RestElement) context.report({
messageId: SPREAD_DESTRUCTURING_VIOLATION,
node: member
});
}
function reportPrototypeViolation(context, node) {
if (node.property.type === AST_NODE_TYPES.Identifier && node.property.name === "prototype" && !node.computed) context.report({
messageId: PROTOTYPE_VIOLATION,
node: node.property
});
}
function reportRegexViolation(context, node) {
const token = context.sourceCode.getFirstToken(node);
if (token && token.type === AST_TOKEN_TYPES.RegularExpression) context.report({
messageId: REGEX_LITERAL_VIOLATION,
node
});
}
const noUnsupportedSyntax = createEslintRule({
create: create$5,
defaultOptions: [],
meta: {
docs: {
description: "Disallow unsupported syntax in roblox-ts",
recommended: true,
requiresTypeChecking: false
},
messages: messages$5,
schema: [],
type: "problem"
},
name: RULE_NAME$5
});
//#endregion
//#region src/rules/no-user-defined-lua-tuple/rule.ts
const RULE_NAME$4 = "no-user-defined-lua-tuple";
const LUA_TUPLE_VIOLATION = "lua-tuple-violation";
const MACRO_VIOLATION = "tuple-macro-violation";
const messages$4 = {
[LUA_TUPLE_VIOLATION]: "Disallow usage of the LuaTuple type keyword",
[MACRO_VIOLATION]: "Disallow usage of the $tuple(...) call"
};
function create$4(context) {
const { allowTupleMacro = false, shouldFix = true } = context.options[0] ?? {};
return {
...!allowTupleMacro && { "CallExpression[callee.type=\"Identifier\"][callee.name=\"$tuple\"]"(node) {
report(context, node.callee, MACRO_VIOLATION, (fixer) => fixTupleMacroCall(node, context, fixer));
} },
"TSInterfaceDeclaration[id.name=\"LuaTuple\"]": (node) => {
report(context, node.id, LUA_TUPLE_VIOLATION);
},
"TSTypeAliasDeclaration[id.name=\"LuaTuple\"]": (node) => {
report(context, node.id, LUA_TUPLE_VIOLATION);
},
"TSTypeReference[typeName.name=\"LuaTuple\"][typeName.type=\"Identifier\"]": (node) => {
report(context, node.typeName, LUA_TUPLE_VIOLATION, shouldFix ? (fixer) => fixLuaTupleType(node, context, fixer) : null);
}
};
}
function fixLuaTupleType(node, context, fixer) {
const typeArgumentNode = node.typeArguments?.params[0];
if (!typeArgumentNode) return null;
const { sourceCode } = context;
const typeArgumentText = sourceCode.getText(typeArgumentNode);
const { parent } = node;
if (parent.type === TSESTree.AST_NODE_TYPES.TSAsExpression && parent.typeAnnotation === node) {
const asExpression = parent;
return fixer.replaceTextRange([asExpression.expression.range[1], asExpression.range[1]], "");
}
return fixer.replaceText(node, typeArgumentText);
}
function fixTupleMacroCall(node, context, fixer) {
const { arguments: args } = node;
if (args.length === 0) return fixer.replaceText(node, "[]");
const { sourceCode } = context;
const tupleElements = args.map((argument) => sourceCode.getText(argument)).join(", ");
const replacementText = `[${tupleElements}]`;
return fixer.replaceText(node, replacementText);
}
function report(context, node, messageId, fix = null) {
context.report({
fix,
messageId,
node
});
}
const noUserDefinedLuaTuple = createEslintRule({
create: create$4,
defaultOptions: [{
allowTupleMacro: false,
shouldFix: true
}],
meta: {
defaultOptions: [{
allowTupleMacro: false,
shouldFix: true
}],
docs: {
description: "Disallow usage of LuaTuple type keyword and $tuple() calls",
recommended: true,
requiresTypeChecking: false
},
fixable: "code",
hasSuggestions: false,
messages: messages$4,
schema: [{
additionalProperties: false,
properties: {
allowTupleMacro: {
default: false,
description: "Whether to allow the $tuple(...) macro call",
type: "boolean"
},
shouldFix: {
default: true,
description: "Whether to enable auto-fixing in which the `LuaTuple` type is converted to a native TypeScript tuple type",
type: "boolean"
}
},
type: "object"
}],
type: "suggestion"
},
name: RULE_NAME$4
});
//#endregion
//#region src/rules/no-value-typeof/rule.ts
const RULE_NAME$3 = "no-value-typeof";
const TYPEOF_VALUE_VIOLATION = "typeof-value-violation";
const messages$3 = { [TYPEOF_VALUE_VIOLATION]: "'typeof' operator is not supported! Use `typeIs(value, type)` or `typeOf(value)` instead." };
function create$3(context) {
return { UnaryExpression(node) {
if (node.operator === "typeof") context.report({
messageId: TYPEOF_VALUE_VIOLATION,
node
});
} };
}
const noValueTypeof = createEslintRule({
create: create$3,
defaultOptions: [],
meta: {
docs: {
description: "Disallow using `typeof` to check for value types",
recommended: true,
requiresTypeChecking: false
},
messages: messages$3,
schema: [],
type: "problem"
},
name: RULE_NAME$3
});
//#endregion
//#region src/rules/prefer-get-players/rule.ts
const RULE_NAME$2 = "prefer-get-players";
const MESSAGE_ID = "get-players-children-violation";
const messages$2 = { [MESSAGE_ID]: "Use Players.GetPlayers() instead of Players.GetChildren() for more accurate types." };
function check(context, node, callee) {
context.report({
fix: (fixer) => fixer.replaceText(callee.property, "GetPlayers"),
messageId: MESSAGE_ID,
node
});
}
function create$2(context, [{ validateType }]) {
return { CallExpression(node) {
const { callee } = node;
if (callee.type !== AST_NODE_TYPES.MemberExpression || callee.property.type !== AST_NODE_TYPES.Identifier || callee.property.name !== "GetChildren") return;
if (validateType) isPlayersCallExpressionType(context, node, callee);
else isPlayersCallExpression(context, node, callee);
} };
}
function isPlayersCallExpression(context, node, callee) {
if (callee.object.type !== AST_NODE_TYPES.Identifier || callee.object.name !== "Players") return;
check(context, node, callee);
}
function isPlayersCallExpressionType(context, node, callee) {
const parserServices = getParserServices(context);
const type = getConstrainedTypeAtLocation(parserServices, callee.object);
const isPlayersType = isBuiltinSymbolLike(parserServices.program, type, ["Players"]);
if (!isPlayersType) return;
const hasGetPlayersProperty = type.getProperty("GetPlayers");
if (!hasGetPlayersProperty) return;
check(context, node, callee);
}
const preferGetPlayers = createEslintRule({
create: create$2,
defaultOptions: [{ validateType: false }],
meta: {
defaultOptions: [{ validateType: false }],
docs: {
description: "Enforces the use of Players.GetPlayers() instead of Players.GetChildren()",
recommended: false,
requiresTypeChecking: false