@systemfsoftware/stryker-plugins 0.6.0 → 0.7.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/README.md CHANGED
@@ -8,7 +8,21 @@ A Stryker **Ignore** plugin (`effect-schema-declarations`) that skips the _equiv
8
8
 
9
9
  - brand descriptions in `Symbol.for('…')`,
10
10
  - `Schema.TaggedClass` / `Schema.TaggedError` `_tag` identifiers,
11
- - the field schemas of those declarations.
11
+ - the field schemas of those declarations,
12
+ - `optionalWith` default values,
13
+ - the documentation entries of an `annotations({…})` call — `identifier`, `description`, `title`, `documentation`, `examples`,
14
+ - an `annotations({…})` object whose entries are _all_ documentation.
15
+
16
+ The last two are deliberately asymmetric. Replacing a `title` cannot change what a
17
+ schema does, so it is ignored wherever it appears. Emptying the whole object can —
18
+ `annotations({ arbitrary })` holds the generator the property tests draw from, and
19
+ dropping it silently changes what gets generated. So an object is ignored only when
20
+ every entry in it documents; one behaviour-bearing sibling keeps the object mutated
21
+ while its documentation entries stay ignored.
22
+
23
+ `arbitrary`, `pretty`, `equivalence`, `message`, `jsonSchema` and `parseIssueTitle`
24
+ are absent from the documentation set by design: each alters observable behaviour, so
25
+ a surviving mutant of one is a test gap to close, never an equivalent mutant to hide.
12
26
 
13
27
  Schema declarations are **data, not behaviour** (Constitution Article III §4) — mutating them produces unkillable equivalent mutants that drag a mutation score below 100% for no real coverage gap. This plugin removes that noise so the score reflects logic.
14
28
 
@@ -0,0 +1,111 @@
1
+ import { PluginKind, declareValuePlugin } from "@stryker-mutator/api/plugin";
2
+ import { Schema } from "effect";
3
+ //#region src/effect-schema-ignorer/ast-node.schema.ts
4
+ const Identifier = Schema.Struct({
5
+ type: Schema.Literal("Identifier"),
6
+ name: Schema.String
7
+ });
8
+ const StringLiteral = Schema.Struct({
9
+ type: Schema.Literal("StringLiteral"),
10
+ value: Schema.String
11
+ });
12
+ const ObjectExpression = Schema.Struct({ type: Schema.Literal("ObjectExpression") });
13
+ const ArrowFunctionExpression = Schema.Struct({ type: Schema.Literal("ArrowFunctionExpression") });
14
+ const UnknownNode = Schema.Struct({ type: Schema.String });
15
+ const MemberExpression = Schema.suspend(() => Schema.Struct({
16
+ type: Schema.Literal("MemberExpression"),
17
+ object: Schema.suspend(() => AstNode),
18
+ property: Schema.suspend(() => AstNode)
19
+ }));
20
+ const CallExpression = Schema.suspend(() => Schema.Struct({
21
+ type: Schema.Literal("CallExpression"),
22
+ callee: Schema.suspend(() => AstNode),
23
+ arguments: Schema.Array(Schema.suspend(() => AstNode))
24
+ }));
25
+ const AstNode = Schema.suspend(() => Schema.Union(Identifier, StringLiteral, ObjectExpression, ArrowFunctionExpression, MemberExpression, CallExpression, UnknownNode));
26
+ //#endregion
27
+ //#region src/effect-schema-ignorer/schema-declaration-ignore.ts
28
+ const SYMBOL_DESCRIPTION_IGNORED = "Symbol.for() brand description is identity-only data, not behaviour";
29
+ const TAGGED_TAG_IGNORED = "TaggedClass/TaggedError _tag is a declaration discriminant, not behaviour";
30
+ const TAGGED_FIELDS_IGNORED = "TaggedClass/TaggedError field schema is a declaration, not behaviour";
31
+ const OPTIONAL_DEFAULT_IGNORED = "optionalWith default value is config, not behaviour";
32
+ const ANNOTATION_OBJECT_IGNORED = "annotations object holding only documentation is a declaration, not behaviour";
33
+ const ANNOTATION_TEXT_IGNORED = "annotation documentation value is declaration data, not behaviour";
34
+ /**
35
+ * The Effect `Schema` annotations that describe a schema without changing what
36
+ * it does. `arbitrary`, `pretty`, `equivalence`, `message`, `jsonSchema` and
37
+ * `parseIssueTitle` are absent by design: each one alters observable behaviour,
38
+ * so a surviving mutant of one is a test gap to close, never an equivalent
39
+ * mutant to ignore.
40
+ */
41
+ const DocumentationKey = Schema.Literal("identifier", "description", "title", "documentation", "examples");
42
+ const DocumentationProperty = Schema.Struct({
43
+ type: Schema.Literal("ObjectProperty"),
44
+ computed: Schema.Literal(false),
45
+ key: Schema.Union(Schema.Struct({
46
+ type: Schema.Literal("Identifier"),
47
+ name: DocumentationKey
48
+ }), Schema.Struct({
49
+ type: Schema.Literal("StringLiteral"),
50
+ value: DocumentationKey
51
+ })),
52
+ value: Schema.Unknown
53
+ });
54
+ /**
55
+ * An object literal whose every entry documents. One behaviour-bearing entry -
56
+ * an `arbitrary` beside a `title` - fails the schema, so the object keeps its
57
+ * mutants: emptying it would delete a generator, which a test can observe.
58
+ */
59
+ const DocumentationObject = Schema.Struct({
60
+ type: Schema.Literal("ObjectExpression"),
61
+ properties: Schema.NonEmptyArray(DocumentationProperty)
62
+ });
63
+ const TAGGED_FACTORIES = ["TaggedClass", "TaggedError"];
64
+ const isIdentifier = Schema.is(Identifier);
65
+ const isStringLiteral = Schema.is(StringLiteral);
66
+ const isObjectExpression = Schema.is(ObjectExpression);
67
+ const isArrowFunctionExpression = Schema.is(ArrowFunctionExpression);
68
+ const isMemberExpression = Schema.is(MemberExpression);
69
+ const isCallExpression = Schema.is(CallExpression);
70
+ const isDocumentationProperty = Schema.is(DocumentationProperty);
71
+ const isDocumentationObject = Schema.is(DocumentationObject);
72
+ const isNamedMember = (node, object, property) => isMemberExpression(node) && isIdentifier(node.object) && node.object.name === object && isIdentifier(node.property) && node.property.name === property;
73
+ const isSymbolForCallee = (callee) => isNamedMember(callee, "Symbol", "for");
74
+ const isTaggedFactoryReference = (reference) => isMemberExpression(reference) && isIdentifier(reference.property) && TAGGED_FACTORIES.includes(reference.property.name);
75
+ const isTaggedFactoryCallee = (callee) => isCallExpression(callee) && isTaggedFactoryReference(callee.callee);
76
+ const isArgumentOf = (node, parent, index, calleeMatches) => isCallExpression(parent) && calleeMatches(parent.callee) && parent.arguments[index] === node;
77
+ const isOptionalWithCallee = (callee) => isNamedMember(callee, "S", "optionalWith");
78
+ const isAnnotationsCallee = (callee) => isMemberExpression(callee) && isIdentifier(callee.property) && callee.property.name === "annotations";
79
+ const argumentRule = (is, argumentIndex, calleeMatches, reason) => ({
80
+ matches: (node, parent) => is(node) && isArgumentOf(node, parent, argumentIndex, calleeMatches),
81
+ reason
82
+ });
83
+ /**
84
+ * A documentation-keyed entry of an `annotations` call. Unlike the object rule
85
+ * this does not care what sits beside it: `title` is documentation whether or
86
+ * not an `arbitrary` shares the object, because replacing the title cannot
87
+ * change what the schema does. Emptying the whole object could, which is why
88
+ * that rule is the stricter of the two.
89
+ */
90
+ const documentationValueRule = {
91
+ matches: (node, parent, grandparent, ancestor) => isDocumentationProperty(parent) && parent.value === node && isArgumentOf(grandparent, ancestor, 0, isAnnotationsCallee),
92
+ reason: ANNOTATION_TEXT_IGNORED
93
+ };
94
+ const RULES = [
95
+ argumentRule(isStringLiteral, 0, isSymbolForCallee, SYMBOL_DESCRIPTION_IGNORED),
96
+ argumentRule(isStringLiteral, 0, isTaggedFactoryCallee, TAGGED_TAG_IGNORED),
97
+ argumentRule(isObjectExpression, 1, isTaggedFactoryCallee, TAGGED_FIELDS_IGNORED),
98
+ argumentRule(isArrowFunctionExpression, 1, isOptionalWithCallee, OPTIONAL_DEFAULT_IGNORED),
99
+ argumentRule(isDocumentationObject, 0, isAnnotationsCallee, ANNOTATION_OBJECT_IGNORED),
100
+ documentationValueRule
101
+ ];
102
+ const decideSchemaDeclarationIgnore = (node, parent, grandparent, ancestor) => RULES.find((rule) => rule.matches(node, parent, grandparent, ancestor))?.reason;
103
+ //#endregion
104
+ //#region src/effect-schema-ignorer/index.ts
105
+ const strykerPlugins = [declareValuePlugin(PluginKind.Ignore, "effect-schema-declarations", { shouldIgnore(path) {
106
+ const parent = path.parentPath;
107
+ const grandparent = parent?.parentPath;
108
+ return decideSchemaDeclarationIgnore(path.node, parent?.node, grandparent?.node, grandparent?.parentPath?.node);
109
+ } })];
110
+ //#endregion
111
+ export { strykerPlugins as t };
@@ -1,2 +1,2 @@
1
- import { t as strykerPlugins } from "./effect-schema-ignorer-D-acEaX8.mjs";
1
+ import { t as strykerPlugins } from "./effect-schema-ignorer-CYdHdByP.mjs";
2
2
  export { strykerPlugins };
package/dist/index.mjs CHANGED
@@ -1,5 +1,53 @@
1
- import { t as strykerPlugins$1 } from "./effect-schema-ignorer-D-acEaX8.mjs";
1
+ import { t as strykerPlugins$2 } from "./effect-schema-ignorer-CYdHdByP.mjs";
2
+ import { PluginKind, declareValuePlugin } from "@stryker-mutator/api/plugin";
3
+ import { Schema } from "effect";
4
+ //#region src/in-source-test-ignorer/ast-node.schema.ts
5
+ const Identifier = Schema.Struct({
6
+ type: Schema.Literal("Identifier"),
7
+ name: Schema.String
8
+ });
9
+ const AstLike = Schema.Struct({ type: Schema.String });
10
+ const MetaProperty = Schema.Struct({
11
+ type: Schema.Literal("MetaProperty"),
12
+ meta: Identifier,
13
+ property: Identifier
14
+ });
15
+ const ImportMetaMember = Schema.Struct({
16
+ type: Schema.Literal("MemberExpression"),
17
+ object: MetaProperty,
18
+ property: Identifier
19
+ });
20
+ const BinaryExpression = Schema.Struct({
21
+ type: Schema.Literal("BinaryExpression"),
22
+ left: AstLike,
23
+ right: AstLike
24
+ });
25
+ const IfStatement = Schema.Struct({
26
+ type: Schema.Literal("IfStatement"),
27
+ test: AstLike
28
+ });
29
+ //#endregion
30
+ //#region src/in-source-test-ignorer/in-source-test-ignore.ts
31
+ const IN_SOURCE_TEST_IGNORED = "inside an `if (import.meta.vitest)` block — test code, not production behaviour";
32
+ const isImportMetaMember = Schema.is(ImportMetaMember);
33
+ const isBinaryExpression = Schema.is(BinaryExpression);
34
+ const isIfStatement = Schema.is(IfStatement);
35
+ const isImportMetaVitest = (node) => isImportMetaMember(node) && node.object.meta.name === "import" && node.object.property.name === "meta" && node.property.name === "vitest";
36
+ const guardsOnImportMetaVitest = (test) => isImportMetaVitest(test) || isBinaryExpression(test) && (isImportMetaVitest(test.left) || isImportMetaVitest(test.right));
37
+ const isInSourceTestGuard = (node) => isIfStatement(node) && guardsOnImportMetaVitest(node.test);
38
+ const decideInSourceTestIgnore = (ancestors) => {
39
+ for (const ancestor of ancestors) if (isInSourceTestGuard(ancestor)) return IN_SOURCE_TEST_IGNORED;
40
+ };
41
+ //#endregion
42
+ //#region src/in-source-test-ignorer/index.ts
43
+ function* ancestorsOf(path) {
44
+ for (let current = path.parentPath; current; current = current.parentPath) yield current.node;
45
+ }
46
+ const strykerPlugins$1 = [declareValuePlugin(PluginKind.Ignore, "in-source-vitest-block", { shouldIgnore(path) {
47
+ return decideInSourceTestIgnore(ancestorsOf(path));
48
+ } })];
49
+ //#endregion
2
50
  //#region src/mod.ts
3
- const strykerPlugins = [...strykerPlugins$1];
51
+ const strykerPlugins = [...strykerPlugins$2, ...strykerPlugins$1];
4
52
  //#endregion
5
53
  export { strykerPlugins };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@systemfsoftware/stryker-plugins",
3
3
  "license": "MIT",
4
- "version": "0.6.0",
4
+ "version": "0.7.0",
5
5
  "author": "Ryan Lee <drdgvhbh@gmail.com>",
6
6
  "repository": {
7
7
  "type": "git",
@@ -53,7 +53,7 @@
53
53
  "vitest": "^4",
54
54
  "@systemfsoftware/arethetypeswrong-cli": "^1.0.7",
55
55
  "@systemfsoftware/effect-schema-law": "^0.5.0",
56
- "@systemfsoftware/effect-schema-vite": "^1.3.0",
56
+ "@systemfsoftware/effect-schema-vite": "^1.4.0",
57
57
  "@systemfsoftware/oxlint-config": "^0.1.0",
58
58
  "@systemfsoftware/stryker-js-core": "^1.1.6",
59
59
  "@systemfsoftware/tsconfig": "^1.2.6",
@@ -1,78 +0,0 @@
1
- import { PluginKind, declareValuePlugin } from "@stryker-mutator/api/plugin";
2
- import { Schema } from "effect";
3
- //#region src/effect-schema-ignorer/ast-node.schema.ts
4
- const Identifier = Schema.Struct({
5
- type: Schema.Literal("Identifier"),
6
- name: Schema.String
7
- });
8
- const StringLiteral = Schema.Struct({
9
- type: Schema.Literal("StringLiteral"),
10
- value: Schema.String
11
- });
12
- const ObjectExpression = Schema.Struct({ type: Schema.Literal("ObjectExpression") });
13
- const ArrowFunctionExpression = Schema.Struct({ type: Schema.Literal("ArrowFunctionExpression") });
14
- const UnknownNode = Schema.Struct({ type: Schema.String });
15
- const MemberExpression = Schema.suspend(() => Schema.Struct({
16
- type: Schema.Literal("MemberExpression"),
17
- object: Schema.suspend(() => AstNode),
18
- property: Schema.suspend(() => AstNode)
19
- }));
20
- const CallExpression = Schema.suspend(() => Schema.Struct({
21
- type: Schema.Literal("CallExpression"),
22
- callee: Schema.suspend(() => AstNode),
23
- arguments: Schema.Array(Schema.suspend(() => AstNode))
24
- }));
25
- const AstNode = Schema.suspend(() => Schema.Union(Identifier, StringLiteral, ObjectExpression, ArrowFunctionExpression, MemberExpression, CallExpression, UnknownNode));
26
- //#endregion
27
- //#region src/effect-schema-ignorer/schema-declaration-ignore.ts
28
- const SYMBOL_DESCRIPTION_IGNORED = "Symbol.for() brand description is identity-only data, not behaviour";
29
- const TAGGED_TAG_IGNORED = "TaggedClass/TaggedError _tag is a declaration discriminant, not behaviour";
30
- const TAGGED_FIELDS_IGNORED = "TaggedClass/TaggedError field schema is a declaration, not behaviour";
31
- const OPTIONAL_DEFAULT_IGNORED = "optionalWith default value is config, not behaviour";
32
- const TAGGED_FACTORIES = ["TaggedClass", "TaggedError"];
33
- const isIdentifier = Schema.is(Identifier);
34
- const isStringLiteral = Schema.is(StringLiteral);
35
- const isObjectExpression = Schema.is(ObjectExpression);
36
- const isArrowFunctionExpression = Schema.is(ArrowFunctionExpression);
37
- const isMemberExpression = Schema.is(MemberExpression);
38
- const isCallExpression = Schema.is(CallExpression);
39
- const isNamedMember = (node, object, property) => isMemberExpression(node) && isIdentifier(node.object) && node.object.name === object && isIdentifier(node.property) && node.property.name === property;
40
- const isSymbolForCallee = (callee) => isNamedMember(callee, "Symbol", "for");
41
- const isTaggedFactoryReference = (reference) => isMemberExpression(reference) && isIdentifier(reference.property) && TAGGED_FACTORIES.includes(reference.property.name);
42
- const isTaggedFactoryCallee = (callee) => isCallExpression(callee) && isTaggedFactoryReference(callee.callee);
43
- const isArgumentOf = (node, parent, index, calleeMatches) => isCallExpression(parent) && calleeMatches(parent.callee) && parent.arguments[index] === node;
44
- const isOptionalWithCallee = (callee) => isNamedMember(callee, "S", "optionalWith");
45
- const RULES = [
46
- {
47
- is: isStringLiteral,
48
- argumentIndex: 0,
49
- calleeMatches: isSymbolForCallee,
50
- reason: SYMBOL_DESCRIPTION_IGNORED
51
- },
52
- {
53
- is: isStringLiteral,
54
- argumentIndex: 0,
55
- calleeMatches: isTaggedFactoryCallee,
56
- reason: TAGGED_TAG_IGNORED
57
- },
58
- {
59
- is: isObjectExpression,
60
- argumentIndex: 1,
61
- calleeMatches: isTaggedFactoryCallee,
62
- reason: TAGGED_FIELDS_IGNORED
63
- },
64
- {
65
- is: isArrowFunctionExpression,
66
- argumentIndex: 1,
67
- calleeMatches: isOptionalWithCallee,
68
- reason: OPTIONAL_DEFAULT_IGNORED
69
- }
70
- ];
71
- const decideSchemaDeclarationIgnore = (node, parent) => RULES.find((rule) => rule.is(node) && isArgumentOf(node, parent, rule.argumentIndex, rule.calleeMatches))?.reason;
72
- //#endregion
73
- //#region src/effect-schema-ignorer/index.ts
74
- const strykerPlugins = [declareValuePlugin(PluginKind.Ignore, "effect-schema-declarations", { shouldIgnore(path) {
75
- return decideSchemaDeclarationIgnore(path.node, path.parentPath?.node);
76
- } })];
77
- //#endregion
78
- export { strykerPlugins as t };