@grailwind/eslint-plugin 0.1.0-beta.0 → 0.1.0-beta.2
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 +19 -9
- package/package.json +3 -3
- package/src/blazon.js +86 -11
- package/src/rules/charges-in-container.js +24 -3
- package/src/rules/rule-of-tincture.js +40 -11
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @grailwind/eslint-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/pafirmin/grailwind/actions/workflows/test.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@grailwind/eslint-plugin)
|
|
5
|
+
[](https://github.com/pafirmin/grailwind/blob/main/packages/eslint-plugin/LICENSE)
|
|
6
|
+
|
|
7
|
+
ESLint rules for [Grailwind](https://grailwind.dev/) markup in JSX and HTML.
|
|
4
8
|
|
|
5
9
|
> **Beta.** Rules, their options and the `recommended` config may change between releases.
|
|
6
10
|
|
|
@@ -9,7 +13,7 @@ npm install --save-dev @grailwind/eslint-plugin@beta
|
|
|
9
13
|
```
|
|
10
14
|
|
|
11
15
|
It reads the tinctures, ordinaries and charges from your installed `grailwind`, so it needs
|
|
12
|
-
`grailwind` 0.
|
|
16
|
+
`grailwind` 0.6.x. For typos and other class errors, see
|
|
13
17
|
[Linting](https://github.com/pafirmin/grailwind#linting) in the Grailwind README.
|
|
14
18
|
|
|
15
19
|
## Setup
|
|
@@ -53,19 +57,23 @@ export default [
|
|
|
53
57
|
No metal (`or`, `argent`) on metal, and no colour on colour.
|
|
54
58
|
|
|
55
59
|
- An ordinary, chief or bordure is checked against the field on the same element:
|
|
56
|
-
`escutcheon or fess-argent` is metal on metal. So are engrailed and
|
|
57
|
-
(`fess-engrailed-argent`).
|
|
60
|
+
`escutcheon or fess-argent` is metal on metal. So are engrailed, invected and voided ones
|
|
61
|
+
(`fess-engrailed-argent`, `cross-voided-argent`).
|
|
58
62
|
- A fimbriated ordinary is checked against its fimbriation, and the fimbriation against the
|
|
59
63
|
field: `or chevron-sable fimbriated-argent` is metal on metal (Argent on Or).
|
|
60
64
|
- Charges in a `charges` container are checked against the field.
|
|
61
65
|
- Charges in a `charges on` container are checked against the ordinary they lie on.
|
|
62
|
-
- Charges at `in-dexter-chief`, `in-chief` or `in-sinister-chief
|
|
63
|
-
if there is one.
|
|
66
|
+
- Charges at `in-dexter-chief`, `in-chief-point` or `in-sinister-chief`, or in a
|
|
67
|
+
`charges in-chief` container, are checked against the chief, if there is one.
|
|
68
|
+
- A plain canton or inescutcheon, one with nothing on it, is checked against the field it lies
|
|
69
|
+
on. One with charges, an ordinary or a division counts as arms of its own and is exempt.
|
|
64
70
|
|
|
65
71
|
A divided field (`per-pale-or-gules`) has no single tincture, so nothing is checked against it.
|
|
66
72
|
Neither are `<img>` charges, charges with no tincture, or `bordure-*` with a non-heraldic colour.
|
|
67
73
|
Counterchanged ordinaries and charges take the field's tinctures swapped, so they're skipped too.
|
|
68
74
|
|
|
75
|
+
`of-the-first` is checked as the shield's first tincture, and reported if it can't be seen.
|
|
76
|
+
|
|
69
77
|
Furs count by their ground: `ermine` and `erminois` as metals, `ermines` and `pean` as colours.
|
|
70
78
|
`ermine fess-argent` is metal on metal. Authorities differ, so the `furs` option changes this:
|
|
71
79
|
|
|
@@ -84,6 +92,8 @@ A fur left out of the map counts by its ground.
|
|
|
84
92
|
Charges (`mullet`, `bezant`, `charge-[…]` and so on) must be direct children of a `charges`
|
|
85
93
|
container.
|
|
86
94
|
|
|
95
|
+
An arrangement (`in-chief`, `in-pale`, `between` and so on) goes on the container, not a charge.
|
|
96
|
+
|
|
87
97
|
### `arrangement-needs-ordinary`
|
|
88
98
|
|
|
89
99
|
`charges between` and `charges on` take their positions from the ordinary on the shield. Without
|
|
@@ -94,7 +104,7 @@ one, other than a chief, they have nowhere to go.
|
|
|
94
104
|
An element draws one ordinary plus, optionally, a chief. With two, such as
|
|
95
105
|
`fess-azure chevron-gules`, only one shows. Two chiefs are flagged too. A `charges` container
|
|
96
106
|
takes one arrangement: `between`, `on`, `in-pale`, `in-fess`, `in-bend` or `in-bend-sinister`.
|
|
97
|
-
Counterchanged, engrailed, invected and fur ordinaries count as ordinaries.
|
|
107
|
+
Counterchanged, engrailed, invected, voided and fur ordinaries count as ordinaries.
|
|
98
108
|
|
|
99
109
|
A charge at a named point (`in-dexter-chief` and so on) is a separate group in the blazon, so it
|
|
100
110
|
goes in its own `charges` container. Among other charges, it still counts towards their number
|
|
@@ -109,8 +119,8 @@ Modifiers that would otherwise do nothing:
|
|
|
109
119
|
element, e.g. `per-pale-argent-gules`.
|
|
110
120
|
- `counterchanged` on `charges`, or on a charge, needs a divided field on the shield that holds
|
|
111
121
|
the container.
|
|
112
|
-
- `fimbriated-*` needs a plain tinctured ordinary. A chief, and an engrailed, invected,
|
|
113
|
-
counterchanged ordinary, aren't fimbriated.
|
|
122
|
+
- `fimbriated-*` needs a plain tinctured ordinary. A chief, and an engrailed, invected, voided,
|
|
123
|
+
fur or counterchanged ordinary, aren't fimbriated.
|
|
114
124
|
- `counterchanged` doesn't work on a charge with a transform (`rotate-*`, `scale-*`, `skew-*`,
|
|
115
125
|
`translate-*`): the charge shows its own small copy of the field instead.
|
|
116
126
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grailwind/eslint-plugin",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
4
|
"description": "ESLint rules for Grailwind heraldry markup: the rule of tincture and charge structure",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"@html-eslint/parser": "*",
|
|
28
28
|
"eslint": "^9.0.0 || ^10.0.0",
|
|
29
|
-
"grailwind": "^0.
|
|
29
|
+
"grailwind": "^0.6.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependenciesMeta": {
|
|
32
32
|
"@html-eslint/parser": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
|
-
"homepage": "https://
|
|
44
|
+
"homepage": "https://grailwind.dev/",
|
|
45
45
|
"repository": {
|
|
46
46
|
"type": "git",
|
|
47
47
|
"url": "git+https://github.com/pafirmin/grailwind.git",
|
package/src/blazon.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Reading heraldry from a set of class names.
|
|
3
3
|
*/
|
|
4
|
+
import { UNKNOWN } from "./tree.js";
|
|
4
5
|
import {
|
|
5
6
|
ARRANGEMENTS,
|
|
6
7
|
CHARGES,
|
|
@@ -9,10 +10,12 @@ import {
|
|
|
9
10
|
FURS,
|
|
10
11
|
LINES,
|
|
11
12
|
METALS,
|
|
13
|
+
OF_THE_FIRST,
|
|
12
14
|
ORDINARIES,
|
|
13
15
|
POINTS,
|
|
14
16
|
ROUNDELS,
|
|
15
17
|
TINCTURES,
|
|
18
|
+
VOIDABLE,
|
|
16
19
|
} from "grailwind/data";
|
|
17
20
|
|
|
18
21
|
const METAL = new Set(METALS);
|
|
@@ -20,9 +23,11 @@ const COLOUR = new Set(COLOURS);
|
|
|
20
23
|
const FUR = new Set(Object.keys(FURS));
|
|
21
24
|
// Anything a field, ordinary or charge can be painted in.
|
|
22
25
|
const TINCTURE = new Set([...TINCTURES, ...FUR]);
|
|
26
|
+
// What a class can paint in: a tincture, or the field's first.
|
|
27
|
+
const PAINT = new Set([...TINCTURE, OF_THE_FIRST]);
|
|
23
28
|
const CHARGE = new Set([...CHARGES, ...Object.keys(ROUNDELS)]);
|
|
24
|
-
const FIMBRIATIONS = new Set(TINCTURES.map((t) => `fimbriated-${t}`));
|
|
25
|
-
const CHIEF_POINTS = new Set(["in-dexter-chief", "in-chief", "in-sinister-chief"]);
|
|
29
|
+
const FIMBRIATIONS = new Set([...TINCTURES, OF_THE_FIRST].map((t) => `fimbriated-${t}`));
|
|
30
|
+
const CHIEF_POINTS = new Set(["in-dexter-chief", "in-chief-point", "in-sinister-chief"]);
|
|
26
31
|
const POINT_CLASSES = new Set(Object.keys(POINTS).map((point) => `in-${point}`));
|
|
27
32
|
export const ALL_ARRANGEMENTS = ["between", "on", ...ARRANGEMENTS];
|
|
28
33
|
|
|
@@ -30,7 +35,7 @@ export const ALL_ARRANGEMENTS = ["between", "on", ...ARRANGEMENTS];
|
|
|
30
35
|
// `bordure-or`, mapped to its parts. A counterchanged ordinary has no tincture of its own.
|
|
31
36
|
const ORDINARY_CLASSES = new Map();
|
|
32
37
|
for (const ordinary of ORDINARIES) {
|
|
33
|
-
for (const tincture of
|
|
38
|
+
for (const tincture of PAINT) {
|
|
34
39
|
const fur = FUR.has(tincture);
|
|
35
40
|
ORDINARY_CLASSES.set(`${ordinary}-${tincture}`, { ordinary, tincture, fur });
|
|
36
41
|
for (const line of LINES) {
|
|
@@ -43,24 +48,41 @@ for (const ordinary of ORDINARIES) {
|
|
|
43
48
|
counterchanged: true,
|
|
44
49
|
});
|
|
45
50
|
}
|
|
46
|
-
for (const
|
|
51
|
+
for (const ordinary of VOIDABLE) {
|
|
52
|
+
for (const tincture of PAINT) {
|
|
53
|
+
const fur = FUR.has(tincture);
|
|
54
|
+
ORDINARY_CLASSES.set(`${ordinary}-voided-${tincture}`, {
|
|
55
|
+
ordinary,
|
|
56
|
+
tincture,
|
|
57
|
+
fur,
|
|
58
|
+
voided: true,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
ORDINARY_CLASSES.set(`${ordinary}-voided-counterchanged`, {
|
|
62
|
+
ordinary,
|
|
63
|
+
tincture: null,
|
|
64
|
+
counterchanged: true,
|
|
65
|
+
voided: true,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
for (const tincture of [...TINCTURES, OF_THE_FIRST]) {
|
|
47
69
|
ORDINARY_CLASSES.set(`bordure-${tincture}`, { ordinary: "bordure", tincture });
|
|
48
70
|
}
|
|
49
71
|
|
|
50
|
-
// Two furs in one division aren't generated.
|
|
51
|
-
const DIVISION_CLASSES = new
|
|
72
|
+
// Each division class, mapped to its first tincture. Two furs in one division aren't generated.
|
|
73
|
+
const DIVISION_CLASSES = new Map();
|
|
52
74
|
for (const division of DIVISIONS) {
|
|
53
75
|
for (const a of TINCTURE) {
|
|
54
76
|
for (const b of TINCTURE) {
|
|
55
77
|
if (a !== b && !(FUR.has(a) && FUR.has(b))) {
|
|
56
|
-
DIVISION_CLASSES.
|
|
78
|
+
DIVISION_CLASSES.set(`${division}-${a}-${b}`, a);
|
|
57
79
|
}
|
|
58
80
|
}
|
|
59
81
|
}
|
|
60
82
|
}
|
|
61
83
|
|
|
62
84
|
// Only a plain, tinctured ordinary is fimbriated (see ordinaries.js).
|
|
63
|
-
export const isFimbriable = (o) => !o.counterchanged && !o.line && !o.fur;
|
|
85
|
+
export const isFimbriable = (o) => !o.counterchanged && !o.line && !o.fur && !o.voided;
|
|
64
86
|
|
|
65
87
|
export const isFur = (tincture) => FUR.has(tincture);
|
|
66
88
|
|
|
@@ -128,17 +150,66 @@ export function fieldTincture(classes) {
|
|
|
128
150
|
if (isDivided(classes)) {
|
|
129
151
|
return null;
|
|
130
152
|
}
|
|
131
|
-
const tinctures = [...classes].filter((cls) =>
|
|
153
|
+
const tinctures = [...classes].filter((cls) => PAINT.has(cls));
|
|
132
154
|
return tinctures.length === 1 ? tinctures[0] : null;
|
|
133
155
|
}
|
|
134
156
|
|
|
157
|
+
const isEmptyNode = (node) => {
|
|
158
|
+
switch (node.type) {
|
|
159
|
+
case "Comment": {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
case "JSXExpressionContainer": {
|
|
163
|
+
return node.expression.type === "JSXEmptyExpression";
|
|
164
|
+
}
|
|
165
|
+
case "Text":
|
|
166
|
+
case "JSXText": {
|
|
167
|
+
return node.value.trim() === "";
|
|
168
|
+
}
|
|
169
|
+
default: {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// A canton or inescutcheon with nothing on it. Cantons/inescutcheons with their
|
|
176
|
+
// own arms are exempt from rule-of-tincture.
|
|
177
|
+
export function isPlain(el) {
|
|
178
|
+
const { ordinaries, chiefs, bordures } = ordinariesOf(el.classes);
|
|
179
|
+
return (
|
|
180
|
+
fieldTincture(el.classes) !== null &&
|
|
181
|
+
!ordinaries.length &&
|
|
182
|
+
!chiefs.length &&
|
|
183
|
+
!bordures.length &&
|
|
184
|
+
(el.node.children ?? []).every(isEmptyNode)
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// A field's first tincture: its bare tincture, or its division's first.
|
|
189
|
+
const fieldFirst = (classes) => {
|
|
190
|
+
const division = [...classes].find((cls) => DIVISION_CLASSES.has(cls));
|
|
191
|
+
return division ? DIVISION_CLASSES.get(division) : fieldTincture(classes);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// `of-the-first` on an element: the nearest escutcheon's or quarter's first (not a canton's).
|
|
195
|
+
// Null if the shield can't be seen.
|
|
196
|
+
export function firstOf(el) {
|
|
197
|
+
for (let node = el; node && node !== UNKNOWN; node = node.parent) {
|
|
198
|
+
if (node.classes?.has("escutcheon") || node.parent?.classes?.has("quarterly")) {
|
|
199
|
+
const first = fieldFirst(node.classes);
|
|
200
|
+
return first === OF_THE_FIRST ? null : first;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
135
206
|
// A charge's tincture, and the class that gives it, e.g. `bezant` → or.
|
|
136
207
|
export function chargeTincture(classes) {
|
|
137
208
|
const roundel = [...classes].find((cls) => ROUNDELS[cls]);
|
|
138
209
|
if (roundel) {
|
|
139
210
|
return { cls: roundel, tincture: ROUNDELS[roundel] };
|
|
140
211
|
}
|
|
141
|
-
const tinctures = [...classes].filter((cls) =>
|
|
212
|
+
const tinctures = [...classes].filter((cls) => PAINT.has(cls));
|
|
142
213
|
return tinctures.length === 1 ? { cls: tinctures[0], tincture: tinctures[0] } : null;
|
|
143
214
|
}
|
|
144
215
|
|
|
@@ -158,7 +229,11 @@ export const isCharge = (classes) => chargeName(classes) !== null;
|
|
|
158
229
|
|
|
159
230
|
export const pointOf = (classes) => [...classes].find((cls) => POINT_CLASSES.has(cls));
|
|
160
231
|
|
|
161
|
-
|
|
232
|
+
const atChiefPoint = (classes) => [...classes].some((cls) => CHIEF_POINTS.has(cls));
|
|
233
|
+
|
|
234
|
+
// A charge on the chief, if the shield has one: at a chief point, or arranged `in-chief`.
|
|
235
|
+
export const inChief = (container, charge) =>
|
|
236
|
+
atChiefPoint(charge.classes) || container.classes.has("in-chief");
|
|
162
237
|
|
|
163
238
|
export const arrangementsOf = (classes) => ALL_ARRANGEMENTS.filter((name) => classes.has(name));
|
|
164
239
|
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { getElements, UNKNOWN } from "../tree.js";
|
|
2
|
-
import { chargeName } from "../blazon.js";
|
|
2
|
+
import { arrangementsOf, chargeName } from "../blazon.js";
|
|
3
|
+
|
|
4
|
+
// The tail of the `arranged` message: where to place one charge instead, or "" for none.
|
|
5
|
+
const hint = (cls) => {
|
|
6
|
+
return (
|
|
7
|
+
{
|
|
8
|
+
"in-chief": "; to place one charge, use `in-chief-point`",
|
|
9
|
+
"in-base": "; to place one charge, use `in-base-point`",
|
|
10
|
+
}[cls] ?? ""
|
|
11
|
+
);
|
|
12
|
+
};
|
|
3
13
|
|
|
4
14
|
export default {
|
|
5
15
|
meta: {
|
|
6
16
|
type: "problem",
|
|
7
17
|
docs: {
|
|
8
|
-
description: "Require charges to be inside a `charges` container",
|
|
18
|
+
description: "Require charges to be inside a `charges` container, and arrangements on it",
|
|
9
19
|
url: "https://github.com/pafirmin/grailwind/tree/main/packages/eslint-plugin#charges-in-container",
|
|
10
20
|
},
|
|
11
21
|
schema: [],
|
|
12
22
|
messages: {
|
|
13
23
|
outside: "`{{charge}}` must be inside a `charges` container",
|
|
24
|
+
arranged: "`{{cls}}` arranges a `charges` container, not a charge{{hint}}",
|
|
14
25
|
},
|
|
15
26
|
},
|
|
16
27
|
|
|
@@ -21,10 +32,20 @@ export default {
|
|
|
21
32
|
if (!el.classes || el.parent === UNKNOWN || !el.parent.classes) {
|
|
22
33
|
continue;
|
|
23
34
|
}
|
|
35
|
+
const inContainer = el.parent.classes.has("charges");
|
|
24
36
|
const charge = chargeName(el.classes);
|
|
25
|
-
if (charge && !
|
|
37
|
+
if (charge && !inContainer) {
|
|
26
38
|
context.report({ node: el.reportNode, messageId: "outside", data: { charge } });
|
|
27
39
|
}
|
|
40
|
+
if (inContainer) {
|
|
41
|
+
for (const cls of arrangementsOf(el.classes)) {
|
|
42
|
+
context.report({
|
|
43
|
+
node: el.reportNode,
|
|
44
|
+
messageId: "arranged",
|
|
45
|
+
data: { cls, hint: hint(cls) },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
28
49
|
}
|
|
29
50
|
},
|
|
30
51
|
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { FURS } from "grailwind/data";
|
|
2
|
-
import { getElements } from "../tree.js";
|
|
1
|
+
import { FURS, OF_THE_FIRST } from "grailwind/data";
|
|
2
|
+
import { getElements, UNKNOWN } from "../tree.js";
|
|
3
3
|
import {
|
|
4
|
-
atChiefPoint,
|
|
5
4
|
chargeName,
|
|
6
5
|
chargeTincture,
|
|
7
6
|
describe,
|
|
8
7
|
fieldTincture,
|
|
9
8
|
fimbriationOf,
|
|
9
|
+
firstOf,
|
|
10
10
|
furKinds,
|
|
11
|
+
inChief,
|
|
11
12
|
isFimbriable,
|
|
12
13
|
isFur,
|
|
14
|
+
isPlain,
|
|
13
15
|
kind,
|
|
14
16
|
ordinariesOf,
|
|
15
17
|
sameKind,
|
|
@@ -21,7 +23,8 @@ const article = (thing) => (/^[aeiou]/.test(thing) ? `an ${thing}` : `a ${thing}
|
|
|
21
23
|
const KINDS = ["metal", "colour", "exempt"];
|
|
22
24
|
|
|
23
25
|
// e.g. "fess engrailed", as blazoned.
|
|
24
|
-
const name = ({ ordinary, line }) =>
|
|
26
|
+
const name = ({ ordinary, line, voided }) =>
|
|
27
|
+
describe([ordinary, line, voided && "voided"].filter(Boolean).join(" "));
|
|
25
28
|
|
|
26
29
|
// e.g. " (ermine counts as a metal)", or " (ermine and erminois count as metals)".
|
|
27
30
|
const furNote = (tinctures, kind) => {
|
|
@@ -64,22 +67,40 @@ export default {
|
|
|
64
67
|
messages: {
|
|
65
68
|
violation:
|
|
66
69
|
"`{{cls}}` puts {{thing}} {{tincture}} on {{ground}}: {{kind}} on {{kind}}{{note}}",
|
|
70
|
+
invisible:
|
|
71
|
+
"`{{cls}}` puts {{thing}} of the first on {{ground}}, its own tincture: it can't be seen",
|
|
72
|
+
plain:
|
|
73
|
+
"`{{cls}}` puts {{thing}} {{tincture}} on {{ground}}: {{kind}} on {{kind}}{{note}}. Change its tincture, or give it arms of its own",
|
|
67
74
|
},
|
|
68
75
|
},
|
|
69
76
|
|
|
70
77
|
create(context) {
|
|
71
78
|
const furs = furKinds(context.options[0]?.furs);
|
|
72
|
-
|
|
79
|
+
// `el` is the element whose field or ordinary it is, for resolving `of-the-first`.
|
|
80
|
+
const check = (el, node, cls, thing, painted, ground, messageId = "violation") => {
|
|
81
|
+
const first = [painted, ground].includes(OF_THE_FIRST) ? firstOf(el) : null;
|
|
82
|
+
const tincture = painted === OF_THE_FIRST ? first : painted;
|
|
83
|
+
if (ground === OF_THE_FIRST) {
|
|
84
|
+
ground = first;
|
|
85
|
+
}
|
|
86
|
+
if (painted === OF_THE_FIRST && tincture && tincture === ground) {
|
|
87
|
+
context.report({
|
|
88
|
+
node,
|
|
89
|
+
messageId: "invisible",
|
|
90
|
+
data: { cls, thing: article(thing), ground: titleCase(ground) },
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
73
94
|
if (!sameKind(tincture, ground, furs)) {
|
|
74
95
|
return;
|
|
75
96
|
}
|
|
76
97
|
context.report({
|
|
77
98
|
node,
|
|
78
|
-
messageId
|
|
99
|
+
messageId,
|
|
79
100
|
data: {
|
|
80
101
|
cls,
|
|
81
102
|
thing: article(thing),
|
|
82
|
-
tincture: titleCase(tincture),
|
|
103
|
+
tincture: titleCase(tincture) + (painted === OF_THE_FIRST ? " (of the first)" : ""),
|
|
83
104
|
ground: titleCase(ground),
|
|
84
105
|
kind: kind(tincture, furs),
|
|
85
106
|
note: furNote([ground, tincture], kind(tincture, furs)),
|
|
@@ -96,14 +117,22 @@ export default {
|
|
|
96
117
|
const field = fieldTincture(el.classes);
|
|
97
118
|
const { ordinaries, chiefs, bordures } = ordinariesOf(el.classes);
|
|
98
119
|
const fimbriation = fimbriationOf(el.classes);
|
|
120
|
+
|
|
121
|
+
// A plain sub-shield lies on its parent's field. A charged or divided one is arms.
|
|
122
|
+
const sub = ["canton", "inescutcheon"].find((c) => el.classes.has(c));
|
|
123
|
+
if (sub && isPlain(el) && el.parent !== UNKNOWN && el.parent.classes) {
|
|
124
|
+
const ground = fieldTincture(el.parent.classes);
|
|
125
|
+
check(el, el.reportNode, field, `plain ${sub}`, field, ground, "plain");
|
|
126
|
+
}
|
|
99
127
|
const isFimbriated = fimbriation && ordinaries.some(isFimbriable);
|
|
100
128
|
|
|
101
129
|
if (isFimbriated) {
|
|
102
|
-
check(el.reportNode, fimbriation.cls, "fimbriation", fimbriation.tincture, field);
|
|
130
|
+
check(el, el.reportNode, fimbriation.cls, "fimbriation", fimbriation.tincture, field);
|
|
103
131
|
}
|
|
104
132
|
|
|
105
133
|
for (const o of ordinaries) {
|
|
106
134
|
check(
|
|
135
|
+
el,
|
|
107
136
|
el.reportNode,
|
|
108
137
|
o.cls,
|
|
109
138
|
name(o),
|
|
@@ -114,7 +143,7 @@ export default {
|
|
|
114
143
|
|
|
115
144
|
// An ordinary abuts the chief rather than lying on it, so both are checked against the field.
|
|
116
145
|
for (const o of [...chiefs, ...bordures]) {
|
|
117
|
-
check(el.reportNode, o.cls, name(o), o.tincture, field);
|
|
146
|
+
check(el, el.reportNode, o.cls, name(o), o.tincture, field);
|
|
118
147
|
}
|
|
119
148
|
|
|
120
149
|
for (const container of el.children) {
|
|
@@ -137,12 +166,12 @@ export default {
|
|
|
137
166
|
let ground = field;
|
|
138
167
|
if (container.classes.has("on") && ordinaries.length) {
|
|
139
168
|
ground = ordinaries[0].tincture;
|
|
140
|
-
} else if (chiefs.length &&
|
|
169
|
+
} else if (chiefs.length && inChief(container, charge)) {
|
|
141
170
|
ground = chiefs[0].tincture;
|
|
142
171
|
}
|
|
143
172
|
|
|
144
173
|
const thing = describe(chargeName(charge.classes) ?? "charge");
|
|
145
|
-
check(charge.reportNode, found.cls, thing, found.tincture, ground);
|
|
174
|
+
check(el, charge.reportNode, found.cls, thing, found.tincture, ground);
|
|
146
175
|
}
|
|
147
176
|
}
|
|
148
177
|
}
|