@systemfsoftware/stryker-plugins 0.8.0 → 0.10.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
|
@@ -33,15 +33,14 @@ Mutants it recognizes are reported as `Ignored`, each carrying the reason it was
|
|
|
33
33
|
|
|
34
34
|
## What it ignores
|
|
35
35
|
|
|
36
|
-
| Declaration | Example
|
|
37
|
-
| --------------------------------------------------------- |
|
|
38
|
-
| Brand descriptions | `Symbol.for('UserId')`
|
|
39
|
-
| `TaggedClass` / `TaggedError` tags | `S.TaggedClass<A>()('Placed', {…})`
|
|
40
|
-
| The field schemas of those declarations | the `{…}` above
|
|
41
|
-
| `optionalWith` defaults | `S.optionalWith(S.Number, { default: () => 0 })`
|
|
42
|
-
| Documentation annotations | `identifier`, `description`, `title`, `documentation`, `examples`
|
|
43
|
-
| An `annotations({…})` object that is _only_ documentation | `S.annotations({ title: 'Amount' })`
|
|
44
|
-
| A `TemplateLiteral` head a piped `pattern` already forces | `S.TemplateLiteral('usr', S.String).pipe(S.pattern(/^usr[a-z]*$/))` |
|
|
36
|
+
| Declaration | Example |
|
|
37
|
+
| --------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
38
|
+
| Brand descriptions | `Symbol.for('UserId')` |
|
|
39
|
+
| `TaggedClass` / `TaggedError` tags | `S.TaggedClass<A>()('Placed', {…})` |
|
|
40
|
+
| The field schemas of those declarations | the `{…}` above |
|
|
41
|
+
| `optionalWith` defaults | `S.optionalWith(S.Number, { default: () => 0 })` |
|
|
42
|
+
| Documentation annotations | `identifier`, `description`, `title`, `documentation`, `examples` |
|
|
43
|
+
| An `annotations({…})` object that is _only_ documentation | `S.annotations({ title: 'Amount' })` |
|
|
45
44
|
|
|
46
45
|
## Where the line is
|
|
47
46
|
|
|
@@ -49,8 +48,6 @@ Every ignore is proven redundant, never merely assumed — anything a test could
|
|
|
49
48
|
|
|
50
49
|
`arbitrary`, `pretty`, `equivalence`, `message`, `jsonSchema` and `parseIssueTitle` are **not** documentation: each changes what the schema does, so a survivor there is a test gap to close. That is why the two `annotations` rules differ — a `title` is ignored wherever it appears, but the enclosing object is ignored only when every entry documents, since emptying an object holding an `arbitrary` would silently change what your property tests generate.
|
|
51
50
|
|
|
52
|
-
The `TemplateLiteral` head is ignored only where the piped pattern makes it unobservable: the head must be the first span with no regex metacharacters, and the pattern must be an unflagged regex literal opening `^` + that head, with no quantifier making its final character optional. A head with no pattern keeps its mutants, and so do `/^usr?…/`, `/^usr…/i`, `/^usr…/m`, and every span past the first.
|
|
53
|
-
|
|
54
51
|
## Contributing
|
|
55
52
|
|
|
56
53
|
Development setup and workflow: [AGENTS.md](AGENTS.md).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PluginKind, declareValuePlugin } from "@stryker-mutator/api/plugin";
|
|
2
2
|
import { Schema } from "effect";
|
|
3
|
-
//#region src/effect-schema-ignorer/ast-node.
|
|
3
|
+
//#region src/effect-schema-ignorer/ast-node.kernel.ts
|
|
4
4
|
const Identifier = Schema.Struct({
|
|
5
5
|
type: Schema.Literal("Identifier"),
|
|
6
6
|
name: Schema.String
|
|
@@ -9,11 +9,6 @@ const StringLiteral = Schema.Struct({
|
|
|
9
9
|
type: Schema.Literal("StringLiteral"),
|
|
10
10
|
value: Schema.String
|
|
11
11
|
});
|
|
12
|
-
const RegExpLiteral = Schema.Struct({
|
|
13
|
-
type: Schema.Literal("RegExpLiteral"),
|
|
14
|
-
pattern: Schema.String,
|
|
15
|
-
flags: Schema.String
|
|
16
|
-
});
|
|
17
12
|
const ObjectExpression = Schema.Struct({ type: Schema.Literal("ObjectExpression") });
|
|
18
13
|
const ArrowFunctionExpression = Schema.Struct({ type: Schema.Literal("ArrowFunctionExpression") });
|
|
19
14
|
const UnknownNode = Schema.Struct({ type: Schema.String });
|
|
@@ -27,16 +22,15 @@ const CallExpression = Schema.suspend(() => Schema.Struct({
|
|
|
27
22
|
callee: Schema.suspend(() => AstNode),
|
|
28
23
|
arguments: Schema.Array(Schema.suspend(() => AstNode))
|
|
29
24
|
}));
|
|
30
|
-
const AstNode = Schema.suspend(() => Schema.Union(Identifier, StringLiteral,
|
|
25
|
+
const AstNode = Schema.suspend(() => Schema.Union(Identifier, StringLiteral, ObjectExpression, ArrowFunctionExpression, MemberExpression, CallExpression, UnknownNode));
|
|
31
26
|
//#endregion
|
|
32
|
-
//#region src/effect-schema-ignorer/schema-declaration-ignore.ts
|
|
27
|
+
//#region src/effect-schema-ignorer/schema-declaration-ignore.kernel.ts
|
|
33
28
|
const SYMBOL_DESCRIPTION_IGNORED = "Symbol.for() brand description is identity-only data, not behaviour";
|
|
34
29
|
const TAGGED_TAG_IGNORED = "TaggedClass/TaggedError _tag is a declaration discriminant, not behaviour";
|
|
35
30
|
const TAGGED_FIELDS_IGNORED = "TaggedClass/TaggedError field schema is a declaration, not behaviour";
|
|
36
31
|
const OPTIONAL_DEFAULT_IGNORED = "optionalWith default value is config, not behaviour";
|
|
37
32
|
const ANNOTATION_OBJECT_IGNORED = "annotations object holding only documentation is a declaration, not behaviour";
|
|
38
33
|
const ANNOTATION_TEXT_IGNORED = "annotation documentation value is declaration data, not behaviour";
|
|
39
|
-
const TEMPLATE_HEAD_IGNORED = "TemplateLiteral head re-stated by an anchored pattern filter is a declaration of the encoded type, not behaviour";
|
|
40
34
|
/**
|
|
41
35
|
* The Effect `Schema` annotations that describe a schema without changing what
|
|
42
36
|
* it does. `arbitrary`, `pretty`, `equivalence`, `message`, `jsonSchema` and
|
|
@@ -75,7 +69,6 @@ const isMemberExpression = Schema.is(MemberExpression);
|
|
|
75
69
|
const isCallExpression = Schema.is(CallExpression);
|
|
76
70
|
const isDocumentationProperty = Schema.is(DocumentationProperty);
|
|
77
71
|
const isDocumentationObject = Schema.is(DocumentationObject);
|
|
78
|
-
const isRegExpLiteral = Schema.is(RegExpLiteral);
|
|
79
72
|
const isNamedMember = (node, object, property) => isMemberExpression(node) && isIdentifier(node.object) && node.object.name === object && isIdentifier(node.property) && node.property.name === property;
|
|
80
73
|
const isSymbolForCallee = (callee) => isNamedMember(callee, "Symbol", "for");
|
|
81
74
|
const isTaggedFactoryReference = (reference) => isMemberExpression(reference) && isIdentifier(reference.property) && TAGGED_FACTORIES.includes(reference.property.name);
|
|
@@ -83,41 +76,6 @@ const isTaggedFactoryCallee = (callee) => isCallExpression(callee) && isTaggedFa
|
|
|
83
76
|
const isArgumentOf = (node, parent, index, calleeMatches) => isCallExpression(parent) && calleeMatches(parent.callee) && parent.arguments[index] === node;
|
|
84
77
|
const isOptionalWithCallee = (callee) => isNamedMember(callee, "S", "optionalWith");
|
|
85
78
|
const isAnnotationsCallee = (callee) => isMemberExpression(callee) && isIdentifier(callee.property) && callee.property.name === "annotations";
|
|
86
|
-
/**
|
|
87
|
-
* A head whose every character stands for itself inside a regex. `a.b` is
|
|
88
|
-
* excluded because `^a.b` would also admit `axb`, which the head refuses.
|
|
89
|
-
*/
|
|
90
|
-
const REGEX_INERT_HEAD = /^[A-Za-z0-9_]+$/;
|
|
91
|
-
/** `?`, `*` and `{` make the character before them optional, so `^usr?` never forces the `r`. */
|
|
92
|
-
const OPTIONAL_QUANTIFIERS = [
|
|
93
|
-
"?",
|
|
94
|
-
"*",
|
|
95
|
-
"{"
|
|
96
|
-
];
|
|
97
|
-
const isNamedMethodCallee = (callee, method) => isMemberExpression(callee) && isIdentifier(callee.property) && callee.property.name === method;
|
|
98
|
-
/**
|
|
99
|
-
* Whether an unflagged, `^`-anchored regex admits only strings opening with
|
|
100
|
-
* `head`. Flags are refused outright: `i` would admit another casing and `m`
|
|
101
|
-
* would let `^` match a line start, and the head rejects both — so it decides.
|
|
102
|
-
*/
|
|
103
|
-
const forcesPrefix = (regex, head) => isRegExpLiteral(regex) && regex.flags === "" && regex.pattern.startsWith(`^${head}`) && !OPTIONAL_QUANTIFIERS.includes(regex.pattern.charAt(head.length + 1));
|
|
104
|
-
const patternForcesPrefix = (argument, head) => isCallExpression(argument) && isNamedMethodCallee(argument.callee, "pattern") && argument.arguments.some((regex) => forcesPrefix(regex, head));
|
|
105
|
-
/**
|
|
106
|
-
* The head of a `TemplateLiteral` piped straight into a `pattern` that already
|
|
107
|
-
* forces that same prefix. Effect rejects refinements as spans, so a prefixed
|
|
108
|
-
* wire format can only earn its `` `${head}${string}` `` type this way, and the
|
|
109
|
-
* prefix ends up stated twice by construction. Emptying the head then changes
|
|
110
|
-
* nothing observable: the filter still refuses every input the head would have
|
|
111
|
-
* refused, and Effect derives the arbitrary from the filter's regex rather than
|
|
112
|
-
* the head. Only the encoded *type* moves, which no test can see.
|
|
113
|
-
*
|
|
114
|
-
* Restricted to argument 0: a trailing span sits past the anchor, so an
|
|
115
|
-
* anchored prefix proves nothing about it and its mutants stay.
|
|
116
|
-
*/
|
|
117
|
-
const templateHeadRule = {
|
|
118
|
-
matches: (node, parent, grandparent, ancestor) => isStringLiteral(node) && REGEX_INERT_HEAD.test(node.value) && isArgumentOf(node, parent, 0, (callee) => isNamedMethodCallee(callee, "TemplateLiteral")) && isMemberExpression(grandparent) && grandparent.object === parent && isIdentifier(grandparent.property) && grandparent.property.name === "pipe" && isCallExpression(ancestor) && ancestor.callee === grandparent && ancestor.arguments.some((argument) => patternForcesPrefix(argument, node.value)),
|
|
119
|
-
reason: TEMPLATE_HEAD_IGNORED
|
|
120
|
-
};
|
|
121
79
|
const argumentRule = (is, argumentIndex, calleeMatches, reason) => ({
|
|
122
80
|
matches: (node, parent) => is(node) && isArgumentOf(node, parent, argumentIndex, calleeMatches),
|
|
123
81
|
reason
|
|
@@ -139,8 +97,7 @@ const RULES = [
|
|
|
139
97
|
argumentRule(isObjectExpression, 1, isTaggedFactoryCallee, TAGGED_FIELDS_IGNORED),
|
|
140
98
|
argumentRule(isArrowFunctionExpression, 1, isOptionalWithCallee, OPTIONAL_DEFAULT_IGNORED),
|
|
141
99
|
argumentRule(isDocumentationObject, 0, isAnnotationsCallee, ANNOTATION_OBJECT_IGNORED),
|
|
142
|
-
documentationValueRule
|
|
143
|
-
templateHeadRule
|
|
100
|
+
documentationValueRule
|
|
144
101
|
];
|
|
145
102
|
const decideSchemaDeclarationIgnore = (node, parent, grandparent, ancestor) => RULES.find((rule) => rule.matches(node, parent, grandparent, ancestor))?.reason;
|
|
146
103
|
//#endregion
|
|
@@ -151,4 +108,4 @@ const strykerPlugins = [declareValuePlugin(PluginKind.Ignore, "effect-schema-dec
|
|
|
151
108
|
return decideSchemaDeclarationIgnore(path.node, parent?.node, grandparent?.node, grandparent?.parentPath?.node);
|
|
152
109
|
} })];
|
|
153
110
|
//#endregion
|
|
154
|
-
export { strykerPlugins as t };
|
|
111
|
+
export { SYMBOL_DESCRIPTION_IGNORED as a, decideSchemaDeclarationIgnore as c, OPTIONAL_DEFAULT_IGNORED as i, ANNOTATION_OBJECT_IGNORED as n, TAGGED_FIELDS_IGNORED as o, ANNOTATION_TEXT_IGNORED as r, TAGGED_TAG_IGNORED as s, strykerPlugins as t };
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { PluginKind } from '@stryker-mutator/api/plugin';
|
|
2
2
|
import { ValuePlugin } from '@stryker-mutator/api/plugin';
|
|
3
3
|
|
|
4
|
+
export declare const ANNOTATION_OBJECT_IGNORED: 'annotations object holding only documentation is a declaration, not behaviour';
|
|
5
|
+
|
|
6
|
+
export declare const ANNOTATION_TEXT_IGNORED: 'annotation documentation value is declaration data, not behaviour';
|
|
7
|
+
|
|
8
|
+
export declare const decideSchemaDeclarationIgnore: (node: unknown, parent: unknown, grandparent?: unknown, ancestor?: unknown) => string | undefined;
|
|
9
|
+
|
|
10
|
+
export declare const OPTIONAL_DEFAULT_IGNORED: 'optionalWith default value is config, not behaviour';
|
|
11
|
+
|
|
4
12
|
export declare const strykerPlugins: ValuePlugin<PluginKind.Ignore>[];
|
|
5
13
|
|
|
14
|
+
export declare const SYMBOL_DESCRIPTION_IGNORED: 'Symbol.for() brand description is identity-only data, not behaviour';
|
|
15
|
+
|
|
16
|
+
export declare const TAGGED_FIELDS_IGNORED: 'TaggedClass/TaggedError field schema is a declaration, not behaviour';
|
|
17
|
+
|
|
18
|
+
export declare const TAGGED_TAG_IGNORED: 'TaggedClass/TaggedError _tag is a declaration discriminant, not behaviour';
|
|
19
|
+
|
|
6
20
|
export { }
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as strykerPlugins } from "./effect-schema-ignorer-
|
|
2
|
-
export { strykerPlugins };
|
|
1
|
+
import { a as SYMBOL_DESCRIPTION_IGNORED, c as decideSchemaDeclarationIgnore, i as OPTIONAL_DEFAULT_IGNORED, n as ANNOTATION_OBJECT_IGNORED, o as TAGGED_FIELDS_IGNORED, r as ANNOTATION_TEXT_IGNORED, s as TAGGED_TAG_IGNORED, t as strykerPlugins } from "./effect-schema-ignorer-DJv73jAE.mjs";
|
|
2
|
+
export { ANNOTATION_OBJECT_IGNORED, ANNOTATION_TEXT_IGNORED, OPTIONAL_DEFAULT_IGNORED, SYMBOL_DESCRIPTION_IGNORED, TAGGED_FIELDS_IGNORED, TAGGED_TAG_IGNORED, decideSchemaDeclarationIgnore, strykerPlugins };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as strykerPlugins$2 } from "./effect-schema-ignorer-
|
|
1
|
+
import { t as strykerPlugins$2 } from "./effect-schema-ignorer-DJv73jAE.mjs";
|
|
2
2
|
import { PluginKind, declareValuePlugin } from "@stryker-mutator/api/plugin";
|
|
3
3
|
import { Schema } from "effect";
|
|
4
|
-
//#region src/in-source-test-ignorer/ast-node.
|
|
4
|
+
//#region src/in-source-test-ignorer/ast-node.kernel.ts
|
|
5
5
|
const Identifier = Schema.Struct({
|
|
6
6
|
type: Schema.Literal("Identifier"),
|
|
7
7
|
name: Schema.String
|
|
@@ -27,7 +27,7 @@ const IfStatement = Schema.Struct({
|
|
|
27
27
|
test: AstLike
|
|
28
28
|
});
|
|
29
29
|
//#endregion
|
|
30
|
-
//#region src/in-source-test-ignorer/in-source-test-ignore.ts
|
|
30
|
+
//#region src/in-source-test-ignorer/in-source-test-ignore.kernel.ts
|
|
31
31
|
const IN_SOURCE_TEST_IGNORED = "inside an `if (import.meta.vitest)` block — test code, not production behaviour";
|
|
32
32
|
const isImportMetaMember = Schema.is(ImportMetaMember);
|
|
33
33
|
const isBinaryExpression = Schema.is(BinaryExpression);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/stryker-plugins",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.10.0",
|
|
5
5
|
"author": "Ryan Lee <drdgvhbh@gmail.com>",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -51,14 +51,15 @@
|
|
|
51
51
|
"tsdown": "^0.22.9",
|
|
52
52
|
"tstyche": "^7.1.0",
|
|
53
53
|
"vitest": "^4",
|
|
54
|
-
"@systemfsoftware/arethetypeswrong-cli": "^1.0
|
|
55
|
-
"@systemfsoftware/effect-schema-law": "^0.
|
|
54
|
+
"@systemfsoftware/arethetypeswrong-cli": "^1.1.0",
|
|
55
|
+
"@systemfsoftware/effect-schema-law": "^0.6.0",
|
|
56
|
+
"@systemfsoftware/effect-schema-vite": "^1.5.0",
|
|
56
57
|
"@systemfsoftware/oxlint-config": "^0.1.0",
|
|
57
|
-
"@systemfsoftware/effect-
|
|
58
|
-
"@systemfsoftware/stryker-js-core": "^1.
|
|
58
|
+
"@systemfsoftware/effect-gherkin-spec": "^0.5.0",
|
|
59
|
+
"@systemfsoftware/stryker-js-core": "^1.2.0",
|
|
60
|
+
"@systemfsoftware/stryker-js-typescript-checker": "^1.2.0",
|
|
59
61
|
"@systemfsoftware/tsconfig": "^1.2.6",
|
|
60
|
-
"@systemfsoftware/vitest-config": "^0.1.0"
|
|
61
|
-
"@systemfsoftware/stryker-js-typescript-checker": "^1.1.6"
|
|
62
|
+
"@systemfsoftware/vitest-config": "^0.1.0"
|
|
62
63
|
},
|
|
63
64
|
"peerDependencies": {
|
|
64
65
|
"@stryker-mutator/api": "^9.6.1",
|
|
@@ -75,7 +76,6 @@
|
|
|
75
76
|
"test": "vitest run",
|
|
76
77
|
"test:run": "vitest run --coverage",
|
|
77
78
|
"test:types": "tstyche",
|
|
78
|
-
"mutation": "stryker run --incremental",
|
|
79
79
|
"mutation:full": "stryker run",
|
|
80
80
|
"lint": "oxlint . ${AGENT:+--format=unix --quiet}",
|
|
81
81
|
"attw": "attw --pack .",
|