@brighterly/lib-fe-codestyle 0.5.1 → 0.5.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.
@@ -80,6 +80,26 @@ const uiKitReuseFirst = {
80
80
  return out;
81
81
  },
82
82
  };
83
+ /**
84
+ * Blank everything outside `<style>` bodies, line count preserved. Colour tokens are a
85
+ * stylesheet concern: an SVG `fill="#E2E8F0"` in an inline-icon template has no place to
86
+ * put a `$token`, so flagging it prescribes an impossible fix (74 of 81 .vue findings
87
+ * fleet-wide were exactly that, 2026-10-02).
88
+ */
89
+ const onlyStyleBlocks = (source) => {
90
+ let out = '';
91
+ let last = 0;
92
+ const re = /<style\b[^>]*>([\s\S]*?)<\/style>/g;
93
+ let m;
94
+ while ((m = re.exec(source)) !== null) {
95
+ const body = m[1] ?? '';
96
+ const bodyStart = m.index + m[0].length - body.length - '</style>'.length;
97
+ out += source.slice(last, bodyStart).replace(/[^\n]/g, ' ');
98
+ out += body;
99
+ last = bodyStart + body.length;
100
+ }
101
+ return out + source.slice(last).replace(/[^\n]/g, ' ');
102
+ };
83
103
  const scssColourTokens = {
84
104
  id: 'core/C3',
85
105
  title: 'literal colour in SCSS instead of a design token',
@@ -96,7 +116,8 @@ const scssColourTokens = {
96
116
  const palette = setting({ settings }, 'scssTokensFile', 'app/assets/scss/_colors.scss');
97
117
  if (file === palette)
98
118
  return [];
99
- return eachLine(code, (line, n) => {
119
+ const scanned = file.endsWith('.vue') ? onlyStyleBlocks(code) : code;
120
+ return eachLine(scanned, (line, n) => {
100
121
  // `rgba($token, .5)` is correct usage — only a NUMERIC first argument is a literal.
101
122
  const hex = /#[0-9a-fA-F]{3,8}\b/.exec(line);
102
123
  const numericRgb = /\brgba?\(\s*\d[^)]*\)/.exec(line);
@@ -122,12 +143,19 @@ const dateFormatEnum = {
122
143
  return [];
123
144
  // A date pattern: only Y/M/D/H/m/s and separators, and carrying a real token.
124
145
  const FORMAT = /(['"`])((?=[^'"`]*(?:YYYY|DD|MM|HH|mm|ss))[YMDHhmsAaZ\s:/.,-]{5,})\1/;
146
+ // Every letter RUN must be a real dayjs token: 'ADHD / ADD' passes the char class
147
+ // (A, D and H are all tokens) and the DD lookahead, but neither 'ADHD' nor 'ADD'
148
+ // is a token — it is an answer label, not a format (found live in cs-app, 2026-10-02).
149
+ const TOKENS = new Set(['YYYY', 'YY', 'Y', 'MMMM', 'MMM', 'MM', 'M', 'DD', 'D', 'Do', 'dddd', 'ddd', 'dd', 'd', 'HH', 'H', 'hh', 'h', 'mm', 'm', 'ss', 's', 'SSS', 'A', 'a', 'Z', 'ZZ']);
150
+ const isFormat = (s) => (s.match(/[A-Za-z]+/g) ?? []).every(run => TOKENS.has(run));
125
151
  return eachLine(code, (line, n) => {
126
- if (/\.toISOString\s*\(/.test(line)) {
127
- return { file, line: n, message: `toISOString() is always UTC and always carries a time — it shifts the calendar day; format through ${dateModule}` };
152
+ // Only the day-extraction misuse shifts the calendar day. A bare toISOString()
153
+ // (an API timestamp) and the epoch duration trick (.slice(14, 19)) are correct uses.
154
+ if (/\.toISOString\s*\(\s*\)\s*(?:\.split\s*\(\s*['"`]T['"`]\s*\)|\.(?:slice|substring)\s*\(\s*0\s*,\s*10\s*\))/.test(line)) {
155
+ return { file, line: n, message: `toISOString() is always UTC — extracting its date part shifts the calendar day for any visitor west of Greenwich; format through ${dateModule}` };
128
156
  }
129
157
  const m = FORMAT.exec(line);
130
- if (!m)
158
+ if (!m || !isFormat(m[2] ?? ''))
131
159
  return null;
132
160
  return { file, line: n, message: `hand-rolled date format ${m[1]}${m[2]}${m[1]} — use a member of ${dateModule}` };
133
161
  });
@@ -33,16 +33,40 @@ export const commonFixtures = {
33
33
  bad: 'window.addEventListener("resize", onResize)',
34
34
  good: 'useEventListener(window, "resize", onResize)',
35
35
  },
36
- 'core/C3': {
37
- file: 'app/assets/scss/components/Card.scss',
38
- bad: '.card { color: #1f2937; }',
39
- good: '.card { color: $slate-800; }',
40
- },
41
- 'core/C4': {
42
- file: 'app/a.ts',
43
- bad: "const d = dayjs().format('YYYY-MM-DD')",
44
- good: 'const d = dayjs().format(DateFormat.DefaultDate)',
45
- },
36
+ 'core/C3': [
37
+ {
38
+ file: 'app/assets/scss/components/Card.scss',
39
+ bad: '.card { color: #1f2937; }',
40
+ good: '.card { color: $slate-800; }',
41
+ },
42
+ // In a .vue file only the <style> block is a stylesheet: an SVG fill in the
43
+ // template has no place to put a $token, so it must stay quiet.
44
+ {
45
+ file: 'app/components/UI/Icons/IconLeaf.vue',
46
+ bad: '<template><svg><path fill="currentColor" /></svg></template>\n<style scoped lang="scss">\n.icon { color: #e2e8f0; }\n</style>',
47
+ good: '<template><svg><path fill="#E2E8F0" /></svg></template>\n<style scoped lang="scss">\n.icon { color: $slate-200; }\n</style>',
48
+ },
49
+ ],
50
+ 'core/C4': [
51
+ {
52
+ file: 'app/a.ts',
53
+ bad: "const d = dayjs().format('YYYY-MM-DD')",
54
+ good: 'const d = dayjs().format(DateFormat.DefaultDate)',
55
+ },
56
+ // The day-extraction misuse fires; a bare toISOString() (an API timestamp) and the
57
+ // epoch duration trick do not.
58
+ {
59
+ file: 'app/b.ts',
60
+ bad: "const day = new Date().toISOString().split('T')[0]",
61
+ good: 'const generatedAt = dayjs().toISOString()\nconst duration = new Date(s * 1000).toISOString().slice(14, 19)',
62
+ },
63
+ // Every letter run must be a real dayjs token — an answer label that happens to fit
64
+ // the char class ('ADHD / ADD' carries a DD) is not a format string.
65
+ {
66
+ file: 'app/c.ts',
67
+ good: "const option = { code: 'ADHD_ADD', label: 'ADHD / ADD' }",
68
+ },
69
+ ],
46
70
  'core/C6': {
47
71
  file: 'app/assets/scss/components/Card.scss',
48
72
  bad: '.card { font-size: 14px; }',
@@ -42,7 +42,7 @@ export declare const sharedRuleIds: {
42
42
  readonly 'core/C2': "core/C2";
43
43
  /** `warn` — a literal colour (`#hex`, numeric `rgba()`) in SCSS/Vue instead of a palette token. Hardcoded colours survive theme and palette changes silently. Fix: a token from `settings.scssTokensFile`. */
44
44
  readonly 'core/C3': "core/C3";
45
- /** `warn` — a hand-rolled date format string (`'YYYY-MM-DD'`) or `toISOString()`. The date's shape becomes an accident instead of a contract. Fix: the shared enum (`settings.dateFormatModule`). */
45
+ /** `warn` — a hand-rolled date format string (`'YYYY-MM-DD'`) or a `toISOString()` whose date part is extracted (`.split('T')[0]`, `.slice(0, 10)`). The date's shape becomes an accident instead of a contract. Fix: the shared enum (`settings.dateFormatModule`). */
46
46
  readonly 'core/C4': "core/C4";
47
47
  /** `warn` — a type file outside `types/<entity>/{enums,interfaces,types}.ts` (root under `settings.typesRoot`). Root-level or mixed-kind type files accrete unrelated entities. `.d.ts` ambient files are exempt. */
48
48
  readonly 'core/C5': "core/C5";
@@ -42,7 +42,7 @@ export const sharedRuleIds = {
42
42
  'core/C2': 'core/C2',
43
43
  /** `warn` — a literal colour (`#hex`, numeric `rgba()`) in SCSS/Vue instead of a palette token. Hardcoded colours survive theme and palette changes silently. Fix: a token from `settings.scssTokensFile`. */
44
44
  'core/C3': 'core/C3',
45
- /** `warn` — a hand-rolled date format string (`'YYYY-MM-DD'`) or `toISOString()`. The date's shape becomes an accident instead of a contract. Fix: the shared enum (`settings.dateFormatModule`). */
45
+ /** `warn` — a hand-rolled date format string (`'YYYY-MM-DD'`) or a `toISOString()` whose date part is extracted (`.split('T')[0]`, `.slice(0, 10)`). The date's shape becomes an accident instead of a contract. Fix: the shared enum (`settings.dateFormatModule`). */
46
46
  'core/C4': 'core/C4',
47
47
  /** `warn` — a type file outside `types/<entity>/{enums,interfaces,types}.ts` (root under `settings.typesRoot`). Root-level or mixed-kind type files accrete unrelated entities. `.d.ts` ambient files are exempt. */
48
48
  'core/C5': 'core/C5',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brighterly/lib-fe-codestyle",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "description": "Shared invariant-validator engine, rule pack and ESLint config for Brighterly Nuxt frontend apps",