@slip-stream-kit/eslint-plugin 0.11.16 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +762 -35
- package/dist/index.js.map +4 -4
- package/dist/rules/e2e-describe-order/e2e-describe-order.d.ts +6 -0
- package/dist/rules/e2e-describe-order/index.d.ts +1 -0
- package/dist/rules/e2e-file-order/e2e-file-order.d.ts +6 -0
- package/dist/rules/e2e-file-order/index.d.ts +1 -0
- package/dist/rules/e2e-page-object-member-order/e2e-page-object-member-order.d.ts +6 -0
- package/dist/rules/e2e-page-object-member-order/index.d.ts +1 -0
- package/dist/rules/e2e-top-level-describe/e2e-top-level-describe.d.ts +8 -0
- package/dist/rules/e2e-top-level-describe/index.d.ts +1 -0
- package/dist/utils/attached-comments.d.ts +11 -0
- package/dist/utils/playwright-call.d.ts +5 -1
- package/package.json +2 -2
- package/readme.md +123 -0
package/dist/index.js
CHANGED
|
@@ -659,6 +659,165 @@ var resolvePackageType = (packageRoot) => {
|
|
|
659
659
|
return type;
|
|
660
660
|
};
|
|
661
661
|
|
|
662
|
+
// src/utils/playwright-call.ts
|
|
663
|
+
var TEST_MODIFIERS = /* @__PURE__ */ new Set(["only", "skip", "fixme", "fail"]);
|
|
664
|
+
var DESCRIBE_MODIFIERS = /* @__PURE__ */ new Set(["only", "skip", "fixme", "serial", "parallel"]);
|
|
665
|
+
var calleeNames = (callee) => {
|
|
666
|
+
if (callee.type === "Identifier") {
|
|
667
|
+
return [callee.name];
|
|
668
|
+
}
|
|
669
|
+
if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier") {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
const head = calleeNames(callee.object);
|
|
673
|
+
return head === null ? null : [...head, callee.property.name];
|
|
674
|
+
};
|
|
675
|
+
var playwrightCallKind = (node) => {
|
|
676
|
+
const names = calleeNames(node.callee);
|
|
677
|
+
if (names === null || names[0] !== "test") {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
const [, second, ...rest] = names;
|
|
681
|
+
if (second === void 0) {
|
|
682
|
+
return "test";
|
|
683
|
+
}
|
|
684
|
+
if (second === "describe") {
|
|
685
|
+
const isDescribe = rest.every((name) => {
|
|
686
|
+
return DESCRIBE_MODIFIERS.has(name);
|
|
687
|
+
});
|
|
688
|
+
return isDescribe ? "describe" : null;
|
|
689
|
+
}
|
|
690
|
+
return rest.length === 0 && TEST_MODIFIERS.has(second) ? "test" : null;
|
|
691
|
+
};
|
|
692
|
+
var detailsArgument = (node) => {
|
|
693
|
+
const details = node.arguments[1];
|
|
694
|
+
return node.arguments.length >= 3 && details?.type === "ObjectExpression" ? details : null;
|
|
695
|
+
};
|
|
696
|
+
var staticTitle = (node) => {
|
|
697
|
+
const title = node.arguments[0];
|
|
698
|
+
if (title?.type === "Literal" && typeof title.value === "string") {
|
|
699
|
+
return title.value;
|
|
700
|
+
}
|
|
701
|
+
if (title?.type === "TemplateLiteral" && title.expressions.length === 0) {
|
|
702
|
+
return title.quasis[0]?.value.cooked ?? null;
|
|
703
|
+
}
|
|
704
|
+
return null;
|
|
705
|
+
};
|
|
706
|
+
var testStatementNames = (statement) => {
|
|
707
|
+
if (statement.type !== "ExpressionStatement" || statement.expression.type !== "CallExpression") {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
const names = calleeNames(statement.expression.callee);
|
|
711
|
+
return names?.[0] === "test" ? names : null;
|
|
712
|
+
};
|
|
713
|
+
var isInE2ePackage = (context) => {
|
|
714
|
+
const packageRoot = findPackageRoot(context.filename.split("\\").join("/"));
|
|
715
|
+
return packageRoot !== null && resolvePackageType(packageRoot) === "e2e";
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
// src/rules/e2e-describe-order/e2e-describe-order.ts
|
|
719
|
+
var SPEC_FILE = /\.spec\.[cm]?[jt]sx?$/;
|
|
720
|
+
var SETUP_RANK = 0;
|
|
721
|
+
var BODY_RANK = 5;
|
|
722
|
+
var HOOK_RANK = { beforeAll: 1, beforeEach: 2, afterEach: 3, afterAll: 4 };
|
|
723
|
+
var SETUP_CALLS = /* @__PURE__ */ new Set(["use", "configure", "setTimeout", "slow"]);
|
|
724
|
+
var ANNOTATION_CALLS = /* @__PURE__ */ new Set(["skip", "fixme", "fail"]);
|
|
725
|
+
var RANK_NAMES = ["setup", "beforeAll hook", "beforeEach hook", "afterEach hook", "afterAll hook", "test"];
|
|
726
|
+
var isFunction = (node) => {
|
|
727
|
+
return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
|
|
728
|
+
};
|
|
729
|
+
var rankFor = (method, call) => {
|
|
730
|
+
if (method in HOOK_RANK) {
|
|
731
|
+
return HOOK_RANK[method];
|
|
732
|
+
}
|
|
733
|
+
const declaresTest = call.arguments.slice(1).some(isFunction);
|
|
734
|
+
return SETUP_CALLS.has(method) || ANNOTATION_CALLS.has(method) && !declaresTest ? SETUP_RANK : BODY_RANK;
|
|
735
|
+
};
|
|
736
|
+
var rankOf = (statement) => {
|
|
737
|
+
const names = testStatementNames(statement);
|
|
738
|
+
if (names === null) {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
const call = statement.expression;
|
|
742
|
+
const rank = rankFor(names.at(-1), call);
|
|
743
|
+
const title = rank === BODY_RANK ? staticTitle(call) : null;
|
|
744
|
+
const label = title === null ? `\`${names.join(".")}\`` : `\`${names.join(".")}('${title}')\``;
|
|
745
|
+
return { rank, label: `${label} (${RANK_NAMES[rank]})` };
|
|
746
|
+
};
|
|
747
|
+
var describeBody = (call) => {
|
|
748
|
+
const callback = call.arguments.at(-1);
|
|
749
|
+
if (callback?.type !== "ArrowFunctionExpression" && callback?.type !== "FunctionExpression") {
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
return callback.body.type === "BlockStatement" ? callback.body.body : null;
|
|
753
|
+
};
|
|
754
|
+
var e2eDescribeOrder = {
|
|
755
|
+
meta: {
|
|
756
|
+
type: "suggestion",
|
|
757
|
+
docs: {
|
|
758
|
+
description: "Order an e2e `test.describe` body: `test.use` / `configure` / annotations, then hooks beforeAll \u2192 beforeEach \u2192 afterEach \u2192 afterAll, then tests and nested describes.",
|
|
759
|
+
recommended: true,
|
|
760
|
+
url: "https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin#e2e-describe-order"
|
|
761
|
+
},
|
|
762
|
+
schema: [
|
|
763
|
+
{
|
|
764
|
+
type: "object",
|
|
765
|
+
properties: {
|
|
766
|
+
ignore: {
|
|
767
|
+
type: "array",
|
|
768
|
+
items: { type: "string" },
|
|
769
|
+
description: "Optional globs; the rule is skipped for matching files."
|
|
770
|
+
}
|
|
771
|
+
},
|
|
772
|
+
additionalProperties: false
|
|
773
|
+
}
|
|
774
|
+
],
|
|
775
|
+
messages: {
|
|
776
|
+
outOfOrder: "{{label}} comes after {{afterLabel}} in describe {{describe}}. Order a describe body: `test.use` / `test.describe.configure` / annotations, then hooks beforeAll \u2192 beforeEach \u2192 afterEach \u2192 afterAll, then tests and nested describes."
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
create(context) {
|
|
780
|
+
const options = context.options[0] ?? {};
|
|
781
|
+
const filename = context.filename.split("\\").join("/");
|
|
782
|
+
if (!SPEC_FILE.test(filename) || options.ignore?.length && matchesAnyGlob(filename, options.ignore)) {
|
|
783
|
+
return {};
|
|
784
|
+
}
|
|
785
|
+
if (!isInE2ePackage(context)) {
|
|
786
|
+
return {};
|
|
787
|
+
}
|
|
788
|
+
return {
|
|
789
|
+
CallExpression(node) {
|
|
790
|
+
const body = playwrightCallKind(node) === "describe" ? describeBody(node) : null;
|
|
791
|
+
if (body === null) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const title = staticTitle(node);
|
|
795
|
+
const describe = title === null ? "block" : `'${title}'`;
|
|
796
|
+
let latest = null;
|
|
797
|
+
for (const statement of body) {
|
|
798
|
+
const ranked = rankOf(statement);
|
|
799
|
+
if (ranked === null) {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (latest !== null && ranked.rank < latest.rank) {
|
|
803
|
+
context.report({
|
|
804
|
+
node: statement,
|
|
805
|
+
messageId: "outOfOrder",
|
|
806
|
+
data: {
|
|
807
|
+
label: ranked.label,
|
|
808
|
+
afterLabel: latest.label,
|
|
809
|
+
describe
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
} else {
|
|
813
|
+
latest = ranked;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
|
|
662
821
|
// src/rules/e2e-file-layout/e2e-file-layout.ts
|
|
663
822
|
var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
664
823
|
var LEGACY_TEST_SUFFIX = /\.test(\.[cm]?[jt]sx?)$/;
|
|
@@ -763,53 +922,469 @@ var e2eFileLayout = {
|
|
|
763
922
|
}
|
|
764
923
|
};
|
|
765
924
|
|
|
766
|
-
// src/utils/
|
|
767
|
-
var
|
|
768
|
-
|
|
769
|
-
|
|
925
|
+
// src/utils/attached-comments.ts
|
|
926
|
+
var attachedStart = (sourceCode, node) => {
|
|
927
|
+
const previousEndLine = sourceCode.getTokenBefore(node)?.loc.end.line ?? 0;
|
|
928
|
+
let start = node.range[0];
|
|
929
|
+
let startLine = node.loc.start.line;
|
|
930
|
+
for (const comment of sourceCode.getCommentsBefore(node).toReversed()) {
|
|
931
|
+
if (comment.loc.end.line < startLine - 1 || comment.loc.start.line <= previousEndLine) {
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
start = comment.range[0];
|
|
935
|
+
startLine = comment.loc.start.line;
|
|
936
|
+
}
|
|
937
|
+
return start;
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
// src/rules/e2e-file-order/e2e-file-order.ts
|
|
941
|
+
var FILE_KIND = [
|
|
942
|
+
[/\.(?:page|component)\.[cm]?[jt]sx?$/, "pageObject"],
|
|
943
|
+
[/\.fixture\.[cm]?[jt]sx?$/, "fixture"],
|
|
944
|
+
[/\.spec\.[cm]?[jt]sx?$/, "spec"],
|
|
945
|
+
[/\.mock\.[cm]?[jt]sx?$/, "mock"]
|
|
946
|
+
];
|
|
947
|
+
var LIB_DIR = /\/lib\//;
|
|
948
|
+
var UNORDERED_FILE = /\.data\.[cm]?[jt]sx?$/;
|
|
949
|
+
var KIND_OPENING = {
|
|
950
|
+
pageObject: "a page object file opens with its imports, then the class",
|
|
951
|
+
fixture: "a fixture file opens with its imports, then the fixtures type and the `extend` call",
|
|
952
|
+
spec: "a spec file opens with its imports, then its `test.*` calls",
|
|
953
|
+
mock: "a mock file opens with its imports, then the exported mock function",
|
|
954
|
+
lib: "a lib file opens with its imports, then its exports"
|
|
955
|
+
};
|
|
956
|
+
var fileKindOf = (filename) => {
|
|
957
|
+
const match = FILE_KIND.find(([pattern]) => {
|
|
958
|
+
return pattern.test(filename);
|
|
959
|
+
});
|
|
960
|
+
if (match) {
|
|
961
|
+
return match[1];
|
|
962
|
+
}
|
|
963
|
+
return LIB_DIR.test(filename) && !UNORDERED_FILE.test(filename) ? "lib" : null;
|
|
964
|
+
};
|
|
965
|
+
var unwrapExport2 = (statement) => {
|
|
966
|
+
const isExport = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration";
|
|
967
|
+
return isExport && statement.declaration ? statement.declaration : statement;
|
|
968
|
+
};
|
|
969
|
+
var calleeRoot = (callee) => {
|
|
770
970
|
if (callee.type === "Identifier") {
|
|
771
|
-
return
|
|
971
|
+
return callee.name;
|
|
772
972
|
}
|
|
773
|
-
if (callee.type
|
|
973
|
+
if (callee.type === "MemberExpression") {
|
|
974
|
+
return calleeRoot(callee.object);
|
|
975
|
+
}
|
|
976
|
+
return callee.type === "CallExpression" ? calleeRoot(callee.callee) : null;
|
|
977
|
+
};
|
|
978
|
+
var typeNamesOf = (node) => {
|
|
979
|
+
if (node === void 0) {
|
|
980
|
+
return [];
|
|
981
|
+
}
|
|
982
|
+
if (node.type === "TSTypeReference" && node.typeName?.type === "Identifier") {
|
|
983
|
+
return [node.typeName.name];
|
|
984
|
+
}
|
|
985
|
+
return (node.types ?? []).flatMap(typeNamesOf);
|
|
986
|
+
};
|
|
987
|
+
var pageObjectAnchor = (declaration, index) => {
|
|
988
|
+
if (declaration.type !== "ClassDeclaration") {
|
|
774
989
|
return null;
|
|
775
990
|
}
|
|
776
|
-
|
|
777
|
-
return head === null ? null : [...head, callee.property.name];
|
|
991
|
+
return { index, label: `class \`${declaration.id?.name ?? "default"}\``, headerTypes: /* @__PURE__ */ new Set() };
|
|
778
992
|
};
|
|
779
|
-
var
|
|
780
|
-
|
|
781
|
-
if (names === null || names[0] !== "test") {
|
|
993
|
+
var fixtureAnchor = (declaration, index) => {
|
|
994
|
+
if (declaration.type !== "VariableDeclaration") {
|
|
782
995
|
return null;
|
|
783
996
|
}
|
|
784
|
-
const [
|
|
785
|
-
|
|
786
|
-
|
|
997
|
+
const [declarator] = declaration.declarations;
|
|
998
|
+
const init = declarator?.init;
|
|
999
|
+
if (init?.type !== "CallExpression" || init.callee.type !== "MemberExpression") {
|
|
1000
|
+
return null;
|
|
787
1001
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
});
|
|
792
|
-
return isDescribe ? "describe" : null;
|
|
1002
|
+
const property = init.callee.property;
|
|
1003
|
+
if (property.type !== "Identifier" || property.name !== "extend") {
|
|
1004
|
+
return null;
|
|
793
1005
|
}
|
|
794
|
-
|
|
1006
|
+
const name = declarator.id.type === "Identifier" ? declarator.id.name : "test";
|
|
1007
|
+
const typeArguments = init.typeArguments?.params ?? [];
|
|
1008
|
+
return { index, label: `fixture \`${name}\``, headerTypes: new Set(typeArguments.flatMap(typeNamesOf)) };
|
|
795
1009
|
};
|
|
796
|
-
var
|
|
797
|
-
|
|
798
|
-
|
|
1010
|
+
var specAnchor = (statement, index) => {
|
|
1011
|
+
if (statement.type !== "ExpressionStatement" || statement.expression.type !== "CallExpression") {
|
|
1012
|
+
return null;
|
|
1013
|
+
}
|
|
1014
|
+
if (calleeRoot(statement.expression.callee) !== "test") {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
return { index, label: "the first `test.*` call", headerTypes: /* @__PURE__ */ new Set() };
|
|
799
1018
|
};
|
|
800
|
-
var
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1019
|
+
var isFunctionValue = (node) => {
|
|
1020
|
+
return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
|
|
1021
|
+
};
|
|
1022
|
+
var mockAnchor = (statement, index) => {
|
|
1023
|
+
if (statement.type !== "ExportNamedDeclaration" && statement.type !== "ExportDefaultDeclaration") {
|
|
1024
|
+
return null;
|
|
804
1025
|
}
|
|
805
|
-
|
|
806
|
-
|
|
1026
|
+
const declaration = unwrapExport2(statement);
|
|
1027
|
+
if (declaration.type === "FunctionDeclaration") {
|
|
1028
|
+
return { index, label: `mock \`${declaration.id?.name ?? "default"}\``, headerTypes: /* @__PURE__ */ new Set() };
|
|
1029
|
+
}
|
|
1030
|
+
const declarator = declaration.type === "VariableDeclaration" ? declaration.declarations[0] : void 0;
|
|
1031
|
+
if (declarator?.id.type !== "Identifier" || !isFunctionValue(declarator.init)) {
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
return { index, label: `mock \`${declarator.id.name}\``, headerTypes: /* @__PURE__ */ new Set() };
|
|
1035
|
+
};
|
|
1036
|
+
var ANCHOR_FINDERS = {
|
|
1037
|
+
pageObject: (statement, index) => {
|
|
1038
|
+
return pageObjectAnchor(unwrapExport2(statement), index);
|
|
1039
|
+
},
|
|
1040
|
+
fixture: (statement, index) => {
|
|
1041
|
+
return fixtureAnchor(unwrapExport2(statement), index);
|
|
1042
|
+
},
|
|
1043
|
+
spec: specAnchor,
|
|
1044
|
+
mock: mockAnchor
|
|
1045
|
+
};
|
|
1046
|
+
var findAnchor = (body, kind) => {
|
|
1047
|
+
for (const [index, statement] of body.entries()) {
|
|
1048
|
+
const anchor = ANCHOR_FINDERS[kind](statement, index);
|
|
1049
|
+
if (anchor !== null) {
|
|
1050
|
+
return anchor;
|
|
1051
|
+
}
|
|
807
1052
|
}
|
|
808
1053
|
return null;
|
|
809
1054
|
};
|
|
810
|
-
var
|
|
811
|
-
|
|
812
|
-
|
|
1055
|
+
var isDirective = (statement) => {
|
|
1056
|
+
return statement.type === "ExpressionStatement" && statement.expression.type === "Literal" && typeof statement.expression.value === "string";
|
|
1057
|
+
};
|
|
1058
|
+
var anchoredCandidates = (body, kind) => {
|
|
1059
|
+
const anchor = findAnchor(body, kind);
|
|
1060
|
+
if (anchor === null) {
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
const candidates = body.slice(0, anchor.index).flatMap((statement, index) => {
|
|
1064
|
+
const declaration = unwrapExport2(statement);
|
|
1065
|
+
const isHeader = statement.type === "ImportDeclaration" || isDirective(statement) || declaration.id?.name !== void 0 && anchor.headerTypes.has(declaration.id.name);
|
|
1066
|
+
return isHeader ? [] : [index];
|
|
1067
|
+
});
|
|
1068
|
+
return { candidates, target: anchor.label };
|
|
1069
|
+
};
|
|
1070
|
+
var locallyExportedNames = (body) => {
|
|
1071
|
+
const names = body.flatMap((statement) => {
|
|
1072
|
+
if (statement.type !== "ExportNamedDeclaration" || statement.source) {
|
|
1073
|
+
return [];
|
|
1074
|
+
}
|
|
1075
|
+
return statement.specifiers.flatMap((specifier) => {
|
|
1076
|
+
return specifier.local.type === "Identifier" ? [specifier.local.name] : [];
|
|
1077
|
+
});
|
|
1078
|
+
});
|
|
1079
|
+
return new Set(names);
|
|
1080
|
+
};
|
|
1081
|
+
var declaredNames = (statement) => {
|
|
1082
|
+
const declaration = unwrapExport2(statement);
|
|
1083
|
+
if (declaration.type === "VariableDeclaration") {
|
|
1084
|
+
return declaration.declarations.flatMap((declarator) => {
|
|
1085
|
+
return declarator.id.type === "Identifier" ? [declarator.id.name] : [];
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
return declaration.id?.name ? [declaration.id.name] : [];
|
|
1089
|
+
};
|
|
1090
|
+
var libCandidates = (body) => {
|
|
1091
|
+
const exportedNames = locallyExportedNames(body);
|
|
1092
|
+
const isExported = (statement) => {
|
|
1093
|
+
return statement.type.startsWith("Export") || declaredNames(statement).some((name) => {
|
|
1094
|
+
return exportedNames.has(name);
|
|
1095
|
+
});
|
|
1096
|
+
};
|
|
1097
|
+
const lastExport = body.findLastIndex(isExported);
|
|
1098
|
+
if (lastExport === -1) {
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
const candidates = body.slice(0, lastExport).flatMap((statement, index) => {
|
|
1102
|
+
return statement.type === "ImportDeclaration" || isDirective(statement) || isExported(statement) ? [] : [index];
|
|
1103
|
+
});
|
|
1104
|
+
return { candidates, target: "the exports" };
|
|
1105
|
+
};
|
|
1106
|
+
var misplacedStatements = (body, kind) => {
|
|
1107
|
+
return kind === "lib" ? libCandidates(body) : anchoredCandidates(body, kind);
|
|
1108
|
+
};
|
|
1109
|
+
var runsWithCall = (fn, call) => {
|
|
1110
|
+
if (call.callee === fn || playwrightCallKind(call) === "describe") {
|
|
1111
|
+
return true;
|
|
1112
|
+
}
|
|
1113
|
+
return calleeRoot(call.callee) !== "test";
|
|
1114
|
+
};
|
|
1115
|
+
var isFunctionNode = (node) => {
|
|
1116
|
+
return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration";
|
|
1117
|
+
};
|
|
1118
|
+
var runsAtLoad = (node) => {
|
|
1119
|
+
let child = node;
|
|
1120
|
+
let parent = node.parent;
|
|
1121
|
+
while (parent) {
|
|
1122
|
+
if (isFunctionNode(child)) {
|
|
1123
|
+
if (parent.type !== "CallExpression" || !runsWithCall(child, parent)) {
|
|
1124
|
+
return false;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
const tsParent = parent;
|
|
1128
|
+
if (tsParent.type === "PropertyDefinition" && tsParent.value === child && !tsParent.static) {
|
|
1129
|
+
return false;
|
|
1130
|
+
}
|
|
1131
|
+
child = parent;
|
|
1132
|
+
parent = parent.parent;
|
|
1133
|
+
}
|
|
1134
|
+
return true;
|
|
1135
|
+
};
|
|
1136
|
+
var topLevelIndex = (node, body) => {
|
|
1137
|
+
let current = node;
|
|
1138
|
+
while (current.parent && current.parent.type !== "Program") {
|
|
1139
|
+
current = current.parent;
|
|
1140
|
+
}
|
|
1141
|
+
return body.indexOf(current);
|
|
1142
|
+
};
|
|
1143
|
+
var valueReferences = (sourceCode, statement) => {
|
|
1144
|
+
return sourceCode.getDeclaredVariables(unwrapExport2(statement)).flatMap((variable) => {
|
|
1145
|
+
return variable.references.filter((reference) => {
|
|
1146
|
+
const tsReference = reference;
|
|
1147
|
+
return tsReference.isValueReference !== false && reference.identifier !== variable.identifiers[0];
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
};
|
|
1151
|
+
var movableStrays = (sourceCode, body, candidates) => {
|
|
1152
|
+
const movable = new Set(candidates);
|
|
1153
|
+
let changed = true;
|
|
1154
|
+
while (changed) {
|
|
1155
|
+
changed = false;
|
|
1156
|
+
for (const index of movable) {
|
|
1157
|
+
const pinned = valueReferences(sourceCode, body[index]).some((reference) => {
|
|
1158
|
+
const from = topLevelIndex(reference.identifier, body);
|
|
1159
|
+
return !movable.has(from) && (candidates.includes(from) || runsAtLoad(reference.identifier));
|
|
1160
|
+
});
|
|
1161
|
+
if (pinned) {
|
|
1162
|
+
movable.delete(index);
|
|
1163
|
+
changed = true;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return movable;
|
|
1168
|
+
};
|
|
1169
|
+
var describeStatement = (statement) => {
|
|
1170
|
+
const names = declaredNames(statement);
|
|
1171
|
+
if (names.length > 0) {
|
|
1172
|
+
return names.map((name) => {
|
|
1173
|
+
return `\`${name}\``;
|
|
1174
|
+
}).join(", ");
|
|
1175
|
+
}
|
|
1176
|
+
return `a ${unwrapExport2(statement).type} statement`;
|
|
1177
|
+
};
|
|
1178
|
+
var statementSpan = (sourceCode, statement) => {
|
|
1179
|
+
const trailing = sourceCode.getCommentsAfter(statement).filter((comment) => {
|
|
1180
|
+
return comment.loc.start.line === statement.loc.end.line;
|
|
1181
|
+
});
|
|
1182
|
+
return [attachedStart(sourceCode, statement), (trailing.at(-1) ?? statement).range[1]];
|
|
1183
|
+
};
|
|
1184
|
+
var buildFix = (sourceCode, body, movable) => {
|
|
1185
|
+
return (fixer) => {
|
|
1186
|
+
const spans = movable.map((index) => {
|
|
1187
|
+
return statementSpan(sourceCode, body[index]);
|
|
1188
|
+
});
|
|
1189
|
+
const moved = spans.map(([start, end2]) => {
|
|
1190
|
+
return sourceCode.text.slice(start, end2);
|
|
1191
|
+
});
|
|
1192
|
+
const end = sourceCode.text.length;
|
|
1193
|
+
const separator = sourceCode.text.endsWith("\n") ? "\n" : "\n\n";
|
|
1194
|
+
return [
|
|
1195
|
+
...spans.map(([start, stop]) => {
|
|
1196
|
+
const next = sourceCode.text.slice(stop).search(/\S/);
|
|
1197
|
+
return fixer.removeRange([start, next === -1 ? end : stop + next]);
|
|
1198
|
+
}),
|
|
1199
|
+
fixer.insertTextAfterRange([end, end], `${separator}${moved.join("\n\n")}
|
|
1200
|
+
`)
|
|
1201
|
+
];
|
|
1202
|
+
};
|
|
1203
|
+
};
|
|
1204
|
+
var e2eFileOrder = {
|
|
1205
|
+
meta: {
|
|
1206
|
+
type: "suggestion",
|
|
1207
|
+
fixable: "code",
|
|
1208
|
+
docs: {
|
|
1209
|
+
description: "Require e2e page objects, fixtures, specs, mocks and lib files to open with their imports followed by the class, the `extend` call, the `test.*` calls, the mock function or the exports \u2014 helpers and constants go below.",
|
|
1210
|
+
recommended: true,
|
|
1211
|
+
url: "https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin#e2e-file-order"
|
|
1212
|
+
},
|
|
1213
|
+
schema: [
|
|
1214
|
+
{
|
|
1215
|
+
type: "object",
|
|
1216
|
+
properties: {
|
|
1217
|
+
ignore: {
|
|
1218
|
+
type: "array",
|
|
1219
|
+
items: { type: "string" },
|
|
1220
|
+
description: "Optional globs; the rule is skipped for matching files."
|
|
1221
|
+
}
|
|
1222
|
+
},
|
|
1223
|
+
additionalProperties: false
|
|
1224
|
+
}
|
|
1225
|
+
],
|
|
1226
|
+
messages: {
|
|
1227
|
+
strayBeforeAnchor: "{{name}} sits between the imports and {{anchor}}. Move it below {{anchor}}: {{opening}}. The autofix moves it to the end of the file."
|
|
1228
|
+
}
|
|
1229
|
+
},
|
|
1230
|
+
create(context) {
|
|
1231
|
+
const options = context.options[0] ?? {};
|
|
1232
|
+
const filename = context.filename.split("\\").join("/");
|
|
1233
|
+
const kind = fileKindOf(filename);
|
|
1234
|
+
if (kind === null || options.ignore?.length && matchesAnyGlob(filename, options.ignore)) {
|
|
1235
|
+
return {};
|
|
1236
|
+
}
|
|
1237
|
+
if (!isInE2ePackage(context)) {
|
|
1238
|
+
return {};
|
|
1239
|
+
}
|
|
1240
|
+
return {
|
|
1241
|
+
Program(program) {
|
|
1242
|
+
const body = program.body;
|
|
1243
|
+
const misplaced = misplacedStatements(body, kind);
|
|
1244
|
+
if (misplaced === null) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
const movable = [...movableStrays(context.sourceCode, body, misplaced.candidates)].toSorted((a, b) => {
|
|
1248
|
+
return a - b;
|
|
1249
|
+
});
|
|
1250
|
+
if (movable.length === 0) {
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
const fix = buildFix(context.sourceCode, body, movable);
|
|
1254
|
+
for (const index of movable) {
|
|
1255
|
+
context.report({
|
|
1256
|
+
node: body[index],
|
|
1257
|
+
messageId: "strayBeforeAnchor",
|
|
1258
|
+
data: { name: describeStatement(body[index]), anchor: misplaced.target, opening: KIND_OPENING[kind] },
|
|
1259
|
+
fix
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
|
|
1267
|
+
// src/rules/e2e-page-object-member-order/e2e-page-object-member-order.ts
|
|
1268
|
+
var PAGE_OBJECT_FILE = /\.(?:page|component)\.[cm]?[jt]sx?$/;
|
|
1269
|
+
var GROUPS = ["field", "constructor", "getter", "public method", "private method"];
|
|
1270
|
+
var isPrivate = (member) => {
|
|
1271
|
+
return member.accessibility === "private" || member.accessibility === "protected" || member.key?.type === "PrivateIdentifier";
|
|
1272
|
+
};
|
|
1273
|
+
var groupOf = (member) => {
|
|
1274
|
+
if (member.kind === "constructor") {
|
|
1275
|
+
return "constructor";
|
|
1276
|
+
}
|
|
1277
|
+
if (member.type !== "MethodDefinition") {
|
|
1278
|
+
return "field";
|
|
1279
|
+
}
|
|
1280
|
+
if (isPrivate(member)) {
|
|
1281
|
+
return "private method";
|
|
1282
|
+
}
|
|
1283
|
+
return member.kind === "get" || member.kind === "set" ? "getter" : "public method";
|
|
1284
|
+
};
|
|
1285
|
+
var memberName = (member) => {
|
|
1286
|
+
if (member.kind === "constructor") {
|
|
1287
|
+
return "the constructor";
|
|
1288
|
+
}
|
|
1289
|
+
const key = member.key;
|
|
1290
|
+
if (key?.type === "Identifier") {
|
|
1291
|
+
return `\`${key.name}\``;
|
|
1292
|
+
}
|
|
1293
|
+
return key?.type === "PrivateIdentifier" ? `\`#${key.name}\`` : "a computed member";
|
|
1294
|
+
};
|
|
1295
|
+
var buildFix2 = (sourceCode, members) => {
|
|
1296
|
+
return (fixer) => {
|
|
1297
|
+
const spans = members.map((_, index) => {
|
|
1298
|
+
return [attachedStart(sourceCode, members[index]), members[index].range[1]];
|
|
1299
|
+
});
|
|
1300
|
+
const texts = spans.map(([start, end]) => {
|
|
1301
|
+
return sourceCode.text.slice(start, end);
|
|
1302
|
+
});
|
|
1303
|
+
const order = members.map((member, index) => {
|
|
1304
|
+
return { index, rank: GROUPS.indexOf(groupOf(member)) };
|
|
1305
|
+
}).toSorted((a, b) => {
|
|
1306
|
+
return a.rank - b.rank || a.index - b.index;
|
|
1307
|
+
});
|
|
1308
|
+
return spans.map((span, slot) => {
|
|
1309
|
+
return fixer.replaceTextRange(span, texts[order[slot].index]);
|
|
1310
|
+
});
|
|
1311
|
+
};
|
|
1312
|
+
};
|
|
1313
|
+
var findViolations = (members) => {
|
|
1314
|
+
const violations = [];
|
|
1315
|
+
let latest = null;
|
|
1316
|
+
for (const member of members) {
|
|
1317
|
+
const group = groupOf(member);
|
|
1318
|
+
if (latest !== null && GROUPS.indexOf(group) < GROUPS.indexOf(latest.group)) {
|
|
1319
|
+
violations.push({ member, group, after: latest.member, afterGroup: latest.group });
|
|
1320
|
+
} else {
|
|
1321
|
+
latest = { member, group };
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
return violations;
|
|
1325
|
+
};
|
|
1326
|
+
var e2ePageObjectMemberOrder = {
|
|
1327
|
+
meta: {
|
|
1328
|
+
type: "suggestion",
|
|
1329
|
+
fixable: "code",
|
|
1330
|
+
docs: {
|
|
1331
|
+
description: "Order the members of an e2e page object: fields, the constructor, getters, public methods, then private methods.",
|
|
1332
|
+
recommended: true,
|
|
1333
|
+
url: "https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin#e2e-page-object-member-order"
|
|
1334
|
+
},
|
|
1335
|
+
schema: [
|
|
1336
|
+
{
|
|
1337
|
+
type: "object",
|
|
1338
|
+
properties: {
|
|
1339
|
+
ignore: {
|
|
1340
|
+
type: "array",
|
|
1341
|
+
items: { type: "string" },
|
|
1342
|
+
description: "Optional globs; the rule is skipped for matching files."
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
additionalProperties: false
|
|
1346
|
+
}
|
|
1347
|
+
],
|
|
1348
|
+
messages: {
|
|
1349
|
+
outOfOrder: "The {{group}} {{name}} comes after the {{afterGroup}} {{afterName}} in class `{{className}}`. Order a page object: fields, constructor, getters, public methods, private methods."
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
create(context) {
|
|
1353
|
+
const options = context.options[0] ?? {};
|
|
1354
|
+
const filename = context.filename.split("\\").join("/");
|
|
1355
|
+
if (!PAGE_OBJECT_FILE.test(filename) || options.ignore?.length && matchesAnyGlob(filename, options.ignore)) {
|
|
1356
|
+
return {};
|
|
1357
|
+
}
|
|
1358
|
+
if (!isInE2ePackage(context)) {
|
|
1359
|
+
return {};
|
|
1360
|
+
}
|
|
1361
|
+
const check = (node) => {
|
|
1362
|
+
const members = node.body.body;
|
|
1363
|
+
const violations = findViolations(members);
|
|
1364
|
+
if (violations.length === 0) {
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
const fix = buildFix2(context.sourceCode, members);
|
|
1368
|
+
for (const violation of violations) {
|
|
1369
|
+
context.report({
|
|
1370
|
+
node: violation.member,
|
|
1371
|
+
messageId: "outOfOrder",
|
|
1372
|
+
data: {
|
|
1373
|
+
group: violation.group,
|
|
1374
|
+
name: memberName(violation.member),
|
|
1375
|
+
afterGroup: violation.afterGroup,
|
|
1376
|
+
afterName: memberName(violation.after),
|
|
1377
|
+
className: node.id?.name ?? "anonymous"
|
|
1378
|
+
},
|
|
1379
|
+
fix
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
return {
|
|
1384
|
+
ClassDeclaration: check,
|
|
1385
|
+
ClassExpression: check
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
813
1388
|
};
|
|
814
1389
|
|
|
815
1390
|
// src/rules/e2e-test-tags/e2e-test-tags.ts
|
|
@@ -915,6 +1490,150 @@ var e2eTestTitle = {
|
|
|
915
1490
|
}
|
|
916
1491
|
};
|
|
917
1492
|
|
|
1493
|
+
// src/rules/e2e-top-level-describe/e2e-top-level-describe.ts
|
|
1494
|
+
var SPEC_FILE2 = /\.spec\.[cm]?[jt]sx?$/;
|
|
1495
|
+
var DEFAULT_MAX = 1;
|
|
1496
|
+
var topLevelCalls = (body) => {
|
|
1497
|
+
return body.flatMap((statement) => {
|
|
1498
|
+
const names = testStatementNames(statement);
|
|
1499
|
+
if (names === null) {
|
|
1500
|
+
return [];
|
|
1501
|
+
}
|
|
1502
|
+
const expressionStatement = statement;
|
|
1503
|
+
return [{ statement: expressionStatement, call: expressionStatement.expression, names }];
|
|
1504
|
+
});
|
|
1505
|
+
};
|
|
1506
|
+
var describeLabel = (call) => {
|
|
1507
|
+
const title = staticTitle(call);
|
|
1508
|
+
return title === null ? "the `test.describe` block" : `\`test.describe('${title}')\``;
|
|
1509
|
+
};
|
|
1510
|
+
var callbackBlock = (call) => {
|
|
1511
|
+
const callback = call.arguments.at(-1);
|
|
1512
|
+
if (callback?.type !== "ArrowFunctionExpression" && callback?.type !== "FunctionExpression") {
|
|
1513
|
+
return null;
|
|
1514
|
+
}
|
|
1515
|
+
return callback.body.type === "BlockStatement" ? callback.body : null;
|
|
1516
|
+
};
|
|
1517
|
+
var removalRange = (sourceCode, statement) => {
|
|
1518
|
+
const end = statement.range[1];
|
|
1519
|
+
const next = sourceCode.text.slice(end).search(/\S/);
|
|
1520
|
+
return [attachedStart(sourceCode, statement), next === -1 ? sourceCode.text.length : end + next];
|
|
1521
|
+
};
|
|
1522
|
+
var buildFix3 = (sourceCode, moved, block) => {
|
|
1523
|
+
return (fixer) => {
|
|
1524
|
+
const indent = " ".repeat(block.body[0]?.loc?.start.column ?? 2);
|
|
1525
|
+
const texts = moved.map(({ statement }) => {
|
|
1526
|
+
const [start] = removalRange(sourceCode, statement);
|
|
1527
|
+
return sourceCode.text.slice(start, statement.range[1]).split("\n").map((line) => {
|
|
1528
|
+
return line === "" ? line : `${indent}${line}`;
|
|
1529
|
+
}).join("\n");
|
|
1530
|
+
});
|
|
1531
|
+
return [
|
|
1532
|
+
...moved.map(({ statement }) => {
|
|
1533
|
+
return fixer.removeRange(removalRange(sourceCode, statement));
|
|
1534
|
+
}),
|
|
1535
|
+
fixer.insertTextAfterRange([block.range[0], block.range[0] + 1], `
|
|
1536
|
+
${texts.join("\n\n")}
|
|
1537
|
+
`)
|
|
1538
|
+
];
|
|
1539
|
+
};
|
|
1540
|
+
};
|
|
1541
|
+
var e2eTopLevelDescribe = {
|
|
1542
|
+
meta: {
|
|
1543
|
+
type: "suggestion",
|
|
1544
|
+
fixable: "code",
|
|
1545
|
+
docs: {
|
|
1546
|
+
description: "Require an e2e spec to hold one top-level `test.describe`, with its `test.use`, `configure`, hooks and tests inside it.",
|
|
1547
|
+
recommended: true,
|
|
1548
|
+
url: "https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin#e2e-top-level-describe"
|
|
1549
|
+
},
|
|
1550
|
+
schema: [
|
|
1551
|
+
{
|
|
1552
|
+
type: "object",
|
|
1553
|
+
properties: {
|
|
1554
|
+
ignore: {
|
|
1555
|
+
type: "array",
|
|
1556
|
+
items: { type: "string" },
|
|
1557
|
+
description: "Optional globs; the rule is skipped for matching files."
|
|
1558
|
+
},
|
|
1559
|
+
max: {
|
|
1560
|
+
type: "integer",
|
|
1561
|
+
minimum: 1,
|
|
1562
|
+
description: "Top-level `test.describe` blocks a spec may hold."
|
|
1563
|
+
}
|
|
1564
|
+
},
|
|
1565
|
+
additionalProperties: false
|
|
1566
|
+
}
|
|
1567
|
+
],
|
|
1568
|
+
messages: {
|
|
1569
|
+
extraDescribe: "{{describe}} is top-level describe number {{count}} in this spec; the limit is {{max}}. Move it to its own spec file, or nest it inside {{first}}.",
|
|
1570
|
+
outsideDescribe: "`{{call}}` sits at the top level of the spec. Move it inside {{describe}}: a spec holds one top-level describe, and its setup, hooks and tests live inside it.",
|
|
1571
|
+
testOutsideDescribe: "Test {{test}} sits at the top level of the spec. Wrap the spec\u2019s tests in a `test.describe` named after the feature they cover."
|
|
1572
|
+
}
|
|
1573
|
+
},
|
|
1574
|
+
create(context) {
|
|
1575
|
+
const options = context.options[0] ?? {};
|
|
1576
|
+
const filename = context.filename.split("\\").join("/");
|
|
1577
|
+
if (!SPEC_FILE2.test(filename) || options.ignore?.length && matchesAnyGlob(filename, options.ignore)) {
|
|
1578
|
+
return {};
|
|
1579
|
+
}
|
|
1580
|
+
if (!isInE2ePackage(context)) {
|
|
1581
|
+
return {};
|
|
1582
|
+
}
|
|
1583
|
+
const max = options.max ?? DEFAULT_MAX;
|
|
1584
|
+
return {
|
|
1585
|
+
Program(program) {
|
|
1586
|
+
const calls = topLevelCalls(program.body);
|
|
1587
|
+
const describes = calls.filter(({ call }) => {
|
|
1588
|
+
return playwrightCallKind(call) === "describe";
|
|
1589
|
+
});
|
|
1590
|
+
const tests = calls.filter(({ call }) => {
|
|
1591
|
+
return playwrightCallKind(call) === "test" && call.arguments.slice(1).some((argument) => {
|
|
1592
|
+
return argument.type === "ArrowFunctionExpression" || argument.type === "FunctionExpression";
|
|
1593
|
+
});
|
|
1594
|
+
});
|
|
1595
|
+
const setup = calls.filter((entry) => {
|
|
1596
|
+
return !describes.includes(entry) && !tests.includes(entry);
|
|
1597
|
+
});
|
|
1598
|
+
for (const [index, extra] of describes.slice(max).entries()) {
|
|
1599
|
+
context.report({
|
|
1600
|
+
node: extra.statement,
|
|
1601
|
+
messageId: "extraDescribe",
|
|
1602
|
+
data: {
|
|
1603
|
+
describe: describeLabel(extra.call),
|
|
1604
|
+
count: String(max + index + 1),
|
|
1605
|
+
max: String(max),
|
|
1606
|
+
first: describeLabel(describes[0].call)
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
for (const test of tests) {
|
|
1611
|
+
const title = staticTitle(test.call);
|
|
1612
|
+
context.report({
|
|
1613
|
+
node: test.statement,
|
|
1614
|
+
messageId: "testOutsideDescribe",
|
|
1615
|
+
data: { test: title === null ? `\`${test.names.join(".")}\`` : `'${title}'` }
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
const [only] = describes;
|
|
1619
|
+
const block = describes.length === 1 && only ? callbackBlock(only.call) : null;
|
|
1620
|
+
const fix = block === null ? void 0 : buildFix3(context.sourceCode, setup, block);
|
|
1621
|
+
for (const entry of setup) {
|
|
1622
|
+
context.report({
|
|
1623
|
+
node: entry.statement,
|
|
1624
|
+
messageId: "outsideDescribe",
|
|
1625
|
+
data: {
|
|
1626
|
+
call: entry.names.join("."),
|
|
1627
|
+
describe: only ? describeLabel(only.call) : "a `test.describe` block"
|
|
1628
|
+
},
|
|
1629
|
+
fix
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
};
|
|
1636
|
+
|
|
918
1637
|
// src/rules/max-components-per-file/max-components-per-file.ts
|
|
919
1638
|
var DEFAULT_MAX_COMPONENTS = 4;
|
|
920
1639
|
var ANONYMOUS_NAME2 = "component";
|
|
@@ -2396,7 +3115,7 @@ var locationViolation = (dirs, basename) => {
|
|
|
2396
3115
|
data: { basename, target: [...fromSrc(dirs), TESTS_DIR, basename].join("/") }
|
|
2397
3116
|
};
|
|
2398
3117
|
};
|
|
2399
|
-
var
|
|
3118
|
+
var findViolations2 = (filename, ignore) => {
|
|
2400
3119
|
if (!TEST_FILE2.test(filename) || ignore.length > 0 && matchesAnyGlob(filename, ignore)) {
|
|
2401
3120
|
return [];
|
|
2402
3121
|
}
|
|
@@ -2441,7 +3160,7 @@ var testLocation = {
|
|
|
2441
3160
|
},
|
|
2442
3161
|
create(context) {
|
|
2443
3162
|
const options = context.options[0] ?? {};
|
|
2444
|
-
const violations =
|
|
3163
|
+
const violations = findViolations2(context.filename.split("\\").join("/"), options.ignore ?? []);
|
|
2445
3164
|
if (violations.length === 0) {
|
|
2446
3165
|
return {};
|
|
2447
3166
|
}
|
|
@@ -2473,6 +3192,10 @@ var rules = {
|
|
|
2473
3192
|
"package-structure": packageStructure,
|
|
2474
3193
|
"test-location": testLocation,
|
|
2475
3194
|
"e2e-file-layout": e2eFileLayout,
|
|
3195
|
+
"e2e-file-order": e2eFileOrder,
|
|
3196
|
+
"e2e-page-object-member-order": e2ePageObjectMemberOrder,
|
|
3197
|
+
"e2e-describe-order": e2eDescribeOrder,
|
|
3198
|
+
"e2e-top-level-describe": e2eTopLevelDescribe,
|
|
2476
3199
|
"e2e-test-tags": e2eTestTags,
|
|
2477
3200
|
"e2e-test-title": e2eTestTitle
|
|
2478
3201
|
};
|
|
@@ -2482,7 +3205,7 @@ var PLUGIN_NAME = "@wl";
|
|
|
2482
3205
|
var plugin = {
|
|
2483
3206
|
meta: {
|
|
2484
3207
|
name: "@wl/eslint-plugin",
|
|
2485
|
-
version: "0.
|
|
3208
|
+
version: "0.14.0"
|
|
2486
3209
|
},
|
|
2487
3210
|
rules,
|
|
2488
3211
|
configs: {}
|
|
@@ -2592,6 +3315,10 @@ plugin.configs.recommended = [
|
|
|
2592
3315
|
},
|
|
2593
3316
|
rules: {
|
|
2594
3317
|
[`${PLUGIN_NAME}/e2e-file-layout`]: "error",
|
|
3318
|
+
[`${PLUGIN_NAME}/e2e-file-order`]: "error",
|
|
3319
|
+
[`${PLUGIN_NAME}/e2e-page-object-member-order`]: "error",
|
|
3320
|
+
[`${PLUGIN_NAME}/e2e-describe-order`]: "error",
|
|
3321
|
+
[`${PLUGIN_NAME}/e2e-top-level-describe`]: "error",
|
|
2595
3322
|
[`${PLUGIN_NAME}/e2e-test-tags`]: "error",
|
|
2596
3323
|
[`${PLUGIN_NAME}/e2e-test-title`]: "error"
|
|
2597
3324
|
}
|