onsystem 0.10.3 → 0.11.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/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { $ as DEFAULT_TOKENS, A as unrecordedRules, At as relativePath, B as statusKinds, C as countBaseline, D as pruneBaseline, Dt as isInside, E as onLines, Et as SCRIPT_FILE, It as unique, J as ConfigError, L as toDesignMd, M as writeBaseline, O as readBaseline, Ot as matchesGlob, Pt as plural, R as toDtcg, Rt as readProjectConfig, S as applyBaseline, St as isDesignSystemName, T as goneFiles, Y as DEFAULT_DOCS, _ as formatDiagnostics, _t as PACKAGE_FIELD, b as BASELINE_FILE, bt as detectProject, c as silentLogger, et as FALLBACK_TOKENS, g as escapeData, gt as notReadYet, h as uncheckedNotice, ht as libraryTitle, i as componentFiles, j as updateBaseline, jt as toPosix, k as rulesRun, l as stderrLogger, o as loadDesignSystem, ot as excludedBy, q as CONFIG_FILES, rt as RULE_IDS, s as tokenFiles, tt as LEGACY_CONFIG_FILES, ut as rulesFor, v as NAME, w as findMoves, x as LEGACY_BASELINE_FILE, y as VERSION, yt as declaredConfig } from "./design-system-7wZoJsMm.js";
3
- import { a as notReadNotices, i as missingDependencies, o as explainProject, s as librariesLeftOut, t as serveStdio } from "./stdio-B31hp2UZ.js";
4
- import { r as loadTarget } from "./workspace-C5pMS6Xv.js";
2
+ import { $ as DEFAULT_TOKENS, A as unrecordedRules, At as relativePath, B as statusKinds, C as countBaseline, D as pruneBaseline, Dt as isInside, E as onLines, Et as SCRIPT_FILE, It as unique, J as ConfigError, L as toDesignMd, M as writeBaseline, O as readBaseline, Ot as matchesGlob, Pt as plural, R as toDtcg, Rt as readProjectConfig, S as applyBaseline, St as isDesignSystemName, T as goneFiles, Y as DEFAULT_DOCS, _ as formatDiagnostics, _t as PACKAGE_FIELD, b as BASELINE_FILE, bt as detectProject, c as silentLogger, et as FALLBACK_TOKENS, g as escapeData, gt as notReadYet, h as uncheckedNotice, ht as libraryTitle, i as componentFiles, j as updateBaseline, jt as toPosix, k as rulesRun, l as stderrLogger, o as loadDesignSystem, ot as excludedBy, q as CONFIG_FILES, rt as RULE_IDS, s as tokenFiles, tt as LEGACY_CONFIG_FILES, ut as rulesFor, v as NAME, w as findMoves, x as LEGACY_BASELINE_FILE, y as VERSION, yt as declaredConfig } from "./design-system-Csqts7Eq.js";
3
+ import { a as notReadNotices, i as missingDependencies, o as explainProject, s as librariesLeftOut, t as serveStdio } from "./stdio-0TpyqZaD.js";
4
+ import { r as loadTarget } from "./workspace-Dbxubo9n.js";
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { escapePath, glob } from "tinyglobby";
@@ -8146,21 +8146,29 @@ function toDesignMd(tokens, options) {
8146
8146
  const list = tokens.filter((t) => t.category === section.category);
8147
8147
  if (!list.length) continue;
8148
8148
  if (section.key) {
8149
- const values = {};
8149
+ const values = /* @__PURE__ */ new Map();
8150
+ let steps;
8150
8151
  for (const token of list) {
8152
+ if (isSpacingUnit(token)) {
8153
+ steps = spacingSteps(token, lookup);
8154
+ continue;
8155
+ }
8151
8156
  const key = keys.get(token);
8152
8157
  if (key === void 0) continue;
8153
8158
  const target = token.aliasOf ? byName.get(token.aliasOf) : void 0;
8154
- const targetKey = target?.category === token.category && frontMatterValue(target, section.key, lookup) !== void 0 ? keys.get(target) : void 0;
8159
+ const targetKey = section.key !== "typography" && target?.category === token.category && !isSpacingUnit(target) && frontMatterValue(target, section.key, lookup) !== void 0 ? keys.get(target) : void 0;
8155
8160
  const value = targetKey === void 0 ? frontMatterValue(token, section.key, lookup) : `{${section.key}.${targetKey}}`;
8156
8161
  if (value === void 0) continue;
8157
- if (key in values) {
8162
+ if (values.has(key)) {
8158
8163
  warnings.push(`${token.name} is left out of the front matter: another token has its key`);
8159
8164
  continue;
8160
8165
  }
8161
- values[key] = value;
8166
+ values.set(key, value);
8162
8167
  }
8163
- if (Object.keys(values).length) sections[section.key] = values;
8168
+ for (const [key, value] of steps ?? []) if (!values.has(key)) values.set(key, value);
8169
+ const step = (key) => Number.isFinite(Number(key)) ? Number(key) : Infinity;
8170
+ const ordered = steps ? new Map([...values].sort(([a], [b]) => step(a) - step(b) || 0)) : values;
8171
+ if (ordered.size) sections[section.key] = ordered;
8164
8172
  }
8165
8173
  body.push("", `## ${section.heading}`, "", section.intro, "", ...table(list, keys));
8166
8174
  }
@@ -8180,6 +8188,59 @@ function designKeys(tokens) {
8180
8188
  }
8181
8189
  return out;
8182
8190
  }
8191
+ /** Tailwind's spacing unit, `--spacing`, which `p-4` takes 4 times. */
8192
+ function isSpacingUnit(token) {
8193
+ return token.category === "spacing" && token.tailwind === "";
8194
+ }
8195
+ /** The steps of Tailwind's default spacing scale, `p-0.5` to `p-96`, in spacing units. */
8196
+ const SPACING_STEPS = [
8197
+ .5,
8198
+ 1,
8199
+ 1.5,
8200
+ 2,
8201
+ 2.5,
8202
+ 3,
8203
+ 3.5,
8204
+ 4,
8205
+ 5,
8206
+ 6,
8207
+ 7,
8208
+ 8,
8209
+ 9,
8210
+ 10,
8211
+ 11,
8212
+ 12,
8213
+ 14,
8214
+ 16,
8215
+ 20,
8216
+ 24,
8217
+ 28,
8218
+ 32,
8219
+ 36,
8220
+ 40,
8221
+ 44,
8222
+ 48,
8223
+ 52,
8224
+ 56,
8225
+ 60,
8226
+ 64,
8227
+ 72,
8228
+ 80,
8229
+ 96
8230
+ ];
8231
+ /**
8232
+ * Tailwind's spacing unit as the steps it makes: `"4": 1rem` for `p-4`. A DESIGN.md has no
8233
+ * multiplier: a reader takes each spacing entry as a step, so the unit alone (`base: 0.25rem`)
8234
+ * would make 4px the only one.
8235
+ */
8236
+ function spacingSteps(token, lookup) {
8237
+ const plain = PLAIN_LENGTH.exec(token.value.trim());
8238
+ const [amount, unit] = plain ? [Number(plain[1]), plain[2] ?? "px"] : [evaluateLength(token.value, lookup), "px"];
8239
+ const steps = /* @__PURE__ */ new Map();
8240
+ if (amount === void 0 || !Number.isFinite(amount) || amount <= 0) return steps;
8241
+ for (const n of SPACING_STEPS) steps.set(String(n), `${round(n * amount)}${unit}`);
8242
+ return steps;
8243
+ }
8183
8244
  function frontMatterValue(token, key, lookup) {
8184
8245
  const value = token.value.trim();
8185
8246
  switch (key) {
@@ -8343,21 +8404,25 @@ function beforeV4(range) {
8343
8404
  /**
8344
8405
  * DTCG files and the CSS generated from them often describe the same tokens.
8345
8406
  * Merge entries that share a custom property: keep the DTCG name and
8346
- * description, and add the Tailwind mapping from CSS.
8407
+ * description, and add the Tailwind mapping from CSS. A token without one (a
8408
+ * `@theme inline` value, `rounded-sm`) merges with the token of its name and
8409
+ * Tailwind key, such as the one a DTCG file exported from the stylesheet has.
8347
8410
  */
8348
8411
  function mergeTokens(tokens) {
8349
8412
  const overridden = new Set(tokens.filter((t) => t.origin !== "tailwind-default").map((t) => themeKey(t)));
8350
- const byVar = /* @__PURE__ */ new Map();
8413
+ const byKey = /* @__PURE__ */ new Map();
8351
8414
  const out = [];
8352
8415
  for (const token of tokens) {
8353
8416
  if (token.origin === "tailwind-default" && overridden.has(themeKey(token))) continue;
8354
- const existing = token.cssVar ? byVar.get(token.cssVar) : void 0;
8417
+ const key = token.cssVar ?? (token.tailwind === void 0 ? void 0 : `${token.name}:${token.tailwind}`);
8418
+ const existing = key === void 0 ? void 0 : byKey.get(key);
8355
8419
  if (!existing) {
8356
- if (token.cssVar) byVar.set(token.cssVar, token);
8420
+ if (key !== void 0) byKey.set(key, token);
8357
8421
  out.push(token);
8358
8422
  continue;
8359
8423
  }
8360
8424
  if (existing.tailwind === void 0 && token.tailwind !== void 0) existing.tailwind = token.tailwind;
8425
+ if (existing.category === "other" && token.category !== "other") existing.category = token.category;
8361
8426
  existing.usage = [.../* @__PURE__ */ new Set([...token.usage, ...existing.usage])];
8362
8427
  existing.description ??= token.description;
8363
8428
  if (token.modes) existing.modes = {
@@ -12243,7 +12308,7 @@ function isObject(value) {
12243
12308
  // package.json
12244
12309
  var name = "onsystem";
12245
12310
  // src/version.ts
12246
- const VERSION = "0.10.3";
12311
+ const VERSION = "0.11.0";
12247
12312
  const NAME = name;
12248
12313
  // src/lint/sarif.ts
12249
12314
  const SCHEMA = "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";
@@ -31,4 +31,6 @@ export interface Call {
31
31
  signal: Int32Array;
32
32
  port: MessagePort;
33
33
  }
34
+ /** Resolves when the looks under way have ended. Nothing waits on them but tests. */
35
+ export declare function looking(): Promise<unknown>;
34
36
  export declare function load(request: LoadRequest): Promise<LoadResponse>;
@@ -1,5 +1,6 @@
1
- import { At as relativePath, i as componentFiles, ot as excludedBy, r as buildModel } from "./design-system-7wZoJsMm.js";
2
- import { r as loadTarget } from "./workspace-C5pMS6Xv.js";
1
+ import { At as relativePath, a as fingerprint, i as componentFiles, ot as excludedBy, r as buildModel } from "./design-system-Csqts7Eq.js";
2
+ import { r as loadTarget } from "./workspace-Dbxubo9n.js";
3
+ import { createHash } from "node:crypto";
3
4
  import fs from "node:fs";
4
5
  import path from "node:path";
5
6
  import { parentPort } from "node:worker_threads";
@@ -8,13 +9,25 @@ import { parentPort } from "node:worker_threads";
8
9
  * The ESLint plugin's loader, in a worker thread. ESLint rules run synchronously, and finding a
9
10
  * project's design system does not (config files written in code, globs, the extraction
10
11
  * cache), so the plugin asks this worker and waits for the answer (see `bridge.ts`).
12
+ *
13
+ * The worker keeps what it loaded and answers from it at once. Once that is two seconds old, the
14
+ * next lint starts a look at the project again (its config, then its design system's files) in
15
+ * the background, and the lints after the look get what it found. Waiting for the look instead
16
+ * held up every lint after a pause in an editor for as long as the look took, which grows with
17
+ * the design system.
11
18
  */
12
19
  /** What the configs said (a design-system package's skipped, or its paths ignored), each said once. */
13
20
  const warned = /* @__PURE__ */ new Set();
14
- /** How long a loaded design system is trusted before its files are looked at again. */
21
+ /** How long what a look found is used before a lint starts the next look. */
15
22
  const FRESH_MS = 2e3;
23
+ /** By where ESLint runs and its settings. */
16
24
  const targets = /* @__PURE__ */ new Map();
25
+ /**
26
+ * By target and project root: two targets can reach the same project with configs of their own
27
+ * (runs from two folders, two ESLint configs), and neither may be answered with the other's.
28
+ */
17
29
  const projects = /* @__PURE__ */ new Map();
30
+ const looks = /* @__PURE__ */ new Set();
18
31
  function realPath(file) {
19
32
  try {
20
33
  return fs.realpathSync.native(file);
@@ -22,46 +35,122 @@ function realPath(file) {
22
35
  return file;
23
36
  }
24
37
  }
25
- async function targetFor({ cwd, root, config }) {
26
- const key = JSON.stringify([
27
- cwd,
28
- root,
29
- config
30
- ]);
31
- const cached = targets.get(key);
32
- if (cached && Date.now() - cached.at < FRESH_MS) return cached.target;
33
- const target = loadTarget({
38
+ function loadTargetFor({ cwd, root, config }) {
39
+ return loadTarget({
34
40
  cwd,
35
41
  root: root === void 0 ? void 0 : path.resolve(cwd, root),
36
42
  config: config === void 0 ? void 0 : path.resolve(cwd, config)
37
43
  });
38
- targets.set(key, {
44
+ }
45
+ function keep(target) {
46
+ target.catch(() => void 0);
47
+ return {
39
48
  at: Date.now(),
40
49
  target
50
+ };
51
+ }
52
+ /** The config a file is checked with: its own project's, from a monorepo root. */
53
+ async function configFor(target, file) {
54
+ return target.workspace ? (await target.workspace.projectFor(file)).config : target.config;
55
+ }
56
+ function versionOf(key, config) {
57
+ return createHash("sha256").update(key).update(JSON.stringify(config)).digest("hex");
58
+ }
59
+ async function build(config) {
60
+ const { model, key = String(Date.now()), dependencies = [] } = await buildModel(config, { cache: true });
61
+ const own = new Set((await componentFiles(config)).map(realPath));
62
+ return {
63
+ at: Date.now(),
64
+ key,
65
+ dependencies,
66
+ version: versionOf(key, config),
67
+ config,
68
+ model,
69
+ own
70
+ };
71
+ }
72
+ /**
73
+ * `project` for `config` as the files are now: the same model when nothing it is built from has
74
+ * changed (a config edit that only changes rules, or no change at all), else a new build.
75
+ */
76
+ async function rebuild(project, config) {
77
+ if (await fingerprint(config, project.dependencies) !== project.key) return build(config);
78
+ const { key, dependencies, model, own } = project;
79
+ return {
80
+ at: Date.now(),
81
+ key,
82
+ dependencies,
83
+ version: versionOf(key, config),
84
+ config,
85
+ model,
86
+ own
87
+ };
88
+ }
89
+ /**
90
+ * Runs a look in the background, held in `looks` until it ends. It starts on a later turn of the
91
+ * event loop, after the lint that started it has its answer: the start of a look (finding the
92
+ * project, reading its config, listing the design system's files) runs synchronously, and run at
93
+ * once it held up that answer for as long.
94
+ */
95
+ function background(look) {
96
+ const running = new Promise((resolve) => setImmediate(resolve)).then(look).catch(() => void 0).finally(() => looks.delete(running));
97
+ looks.add(running);
98
+ return running;
99
+ }
100
+ function projectKey(key, config) {
101
+ return JSON.stringify([key, config.root]);
102
+ }
103
+ /** A build that fails drops the project, so that the next lint builds it again and says why. */
104
+ function lookAtProject(slot, project, config) {
105
+ project.looking = background(async () => {
106
+ try {
107
+ projects.set(slot, await rebuild(project, config));
108
+ } catch (error) {
109
+ if (projects.get(slot) === project) projects.delete(slot);
110
+ throw error;
111
+ }
112
+ });
113
+ return project.looking;
114
+ }
115
+ function lookAtTarget(key, kept, request) {
116
+ kept.looking = background(async () => {
117
+ const loading = loadTargetFor(request);
118
+ await loading.catch(() => void 0);
119
+ targets.set(key, keep(loading));
120
+ const config = await configFor(await loading, request.file);
121
+ const slot = projectKey(key, config);
122
+ const project = projects.get(slot);
123
+ if (project && !project.looking) await lookAtProject(slot, project, config);
41
124
  });
42
- return target;
125
+ return kept.looking;
126
+ }
127
+ /** Resolves when the looks under way have ended. Nothing waits on them but tests. */
128
+ function looking() {
129
+ return Promise.all(looks);
43
130
  }
44
131
  async function load(request) {
45
- const target = await targetFor(request);
46
- const config = target.workspace ? (await target.workspace.projectFor(request.file)).config : target.config;
132
+ const key = JSON.stringify([
133
+ request.cwd,
134
+ request.root,
135
+ request.config
136
+ ]);
137
+ let kept = targets.get(key);
138
+ if (!kept) {
139
+ kept = keep(loadTargetFor(request));
140
+ targets.set(key, kept);
141
+ } else if (!kept.looking && Date.now() - kept.at >= FRESH_MS) lookAtTarget(key, kept, request);
142
+ const config = await configFor(await kept.target, request.file);
47
143
  for (const text of config.warnings ?? []) {
48
144
  if (warned.has(text)) continue;
49
145
  warned.add(text);
50
146
  process.stderr.write(`onsystem: ${text}\n`);
51
147
  }
52
- let project = projects.get(config.root);
53
- if (!project || Date.now() - project.at >= FRESH_MS) {
54
- const { model, key } = await buildModel(config, { cache: true });
55
- const own = new Set((await componentFiles(config)).map(realPath));
56
- project = {
57
- at: Date.now(),
58
- version: key ?? String(Date.now()),
59
- config,
60
- model,
61
- own
62
- };
63
- projects.set(config.root, project);
64
- }
148
+ const slot = projectKey(key, config);
149
+ let project = projects.get(slot);
150
+ if (!project) {
151
+ project = await build(config);
152
+ projects.set(slot, project);
153
+ } else if (!kept.looking && !project.looking && (project.config !== config || Date.now() - project.at >= FRESH_MS)) lookAtProject(slot, project, config);
65
154
  const relative = relativePath(config.root, request.file);
66
155
  const excluded = excludedBy(config, relative, { includeTests: true });
67
156
  if (excluded) return { skip: `excluded by "${excluded}"` };
@@ -89,4 +178,4 @@ parentPort?.on("message", ({ request, signal, port }) => {
89
178
  reply({ error: error.message });
90
179
  });
91
180
  });
92
- export { load };
181
+ export { load, looking };
package/dist/eslint.js CHANGED
@@ -1,4 +1,4 @@
1
- import { At as relativePath, N as RULES, Q as DEFAULT_TEST_FOLDERS, X as DEFAULT_SEVERITY, Z as DEFAULT_TEST_EXCLUDE, t as DesignSystem, v as NAME, y as VERSION } from "./design-system-7wZoJsMm.js";
1
+ import { At as relativePath, N as RULES, Q as DEFAULT_TEST_FOLDERS, X as DEFAULT_SEVERITY, Z as DEFAULT_TEST_EXCLUDE, m as checkSource, rt as RULE_IDS, t as DesignSystem, ut as rulesFor, v as NAME, y as VERSION } from "./design-system-Csqts7Eq.js";
2
2
  import path from "node:path";
3
3
  import { MessageChannel, Worker, receiveMessageOnPort } from "node:worker_threads";
4
4
  // src/eslint/bridge.ts
@@ -51,7 +51,8 @@ function loadSync(request) {
51
51
  * working directory), with the project's config: its `rules` and `overrides` turn rules off
52
52
  * per file, `// onsystem-disable-*` comments suppress findings, and the files `check .` leaves
53
53
  * out (the config's `exclude`, the design system's own components) are not checked. ESLint's
54
- * config sets each rule's severity.
54
+ * config sets each rule's severity, and can set a rule's options (`allow`) over the project's.
55
+ * `onsystem/suppression` reports a misspelled rule in a suppression comment, as check does.
55
56
  */
56
57
  const RULES_PAGE = "https://onsystem.vercel.app/rules";
57
58
  /** Design systems by project root, at the version the loader last reported. */
@@ -101,7 +102,25 @@ function run(cwd, file, text, settings) {
101
102
  if (result.skipped) return { skipped: true };
102
103
  return {
103
104
  result,
104
- fromSystem: (specifier) => ds.lint.isDesignSystemImport(specifier, relative)
105
+ fromSystem: (specifier) => ds.lint.isDesignSystemImport(specifier, relative),
106
+ checkWith(id, options) {
107
+ const own = rulesFor(ds.config, relative)[id];
108
+ const off = {
109
+ severity: "off",
110
+ options: {}
111
+ };
112
+ const rules = Object.fromEntries(RULE_IDS.map((rule) => [rule, rule === id ? {
113
+ ...own,
114
+ options: {
115
+ ...own.options,
116
+ ...options
117
+ }
118
+ } : off]));
119
+ return checkSource(text, ds.lint, {
120
+ filename: relative,
121
+ rules
122
+ });
123
+ }
105
124
  };
106
125
  }
107
126
  /**
@@ -153,27 +172,45 @@ function typeOnly(def) {
153
172
  const kind = (node) => node?.importKind;
154
173
  return def.type === "ImportBinding" && (kind(def.node) === "type" || kind(def.parent) === "type");
155
174
  }
175
+ /** A rule's options in ESLint's config, as in onsystem.config.json: `['error', { allow: ['#fff'] }]`. */
176
+ const OPTIONS_SCHEMA = [{
177
+ type: "object",
178
+ properties: { allow: {
179
+ type: "array",
180
+ items: { type: "string" }
181
+ } },
182
+ additionalProperties: false
183
+ }];
184
+ /**
185
+ * Check's misspelled rule names in `// onsystem-disable-*` comments, which suppress nothing. A rule
186
+ * of its own, so that it is a warning, as check reports it, and can be turned off like the others.
187
+ */
188
+ const SUPPRESSION = "suppression";
156
189
  function createRule(id, description) {
190
+ const suppression = id === SUPPRESSION;
157
191
  return {
158
192
  meta: {
159
193
  type: "problem",
160
194
  docs: {
161
195
  description,
162
- url: `${RULES_PAGE}#${id}`
196
+ url: `${RULES_PAGE}#${suppression ? "suppressing-findings" : id}`
197
+ },
198
+ ...suppression ? {} : {
199
+ fixable: "code",
200
+ hasSuggestions: true
163
201
  },
164
- fixable: "code",
165
- hasSuggestions: true,
166
- schema: []
202
+ schema: suppression ? [] : OPTIONS_SCHEMA
167
203
  },
168
204
  create(context) {
169
205
  const file = path.resolve(context.cwd, context.filename);
170
206
  const settings = context.settings.onsystem ?? {};
207
+ const options = context.options?.[0];
171
208
  return { "Program:exit"(node) {
172
209
  const pass = passFor(context, file, settings);
173
210
  const { outcome } = pass;
174
211
  if ("skipped" in outcome) return;
175
212
  if ("error" in outcome) {
176
- if (pass.reported) return;
213
+ if (suppression || pass.reported) return;
177
214
  pass.reported = true;
178
215
  context.report({
179
216
  node,
@@ -181,7 +218,8 @@ function createRule(id, description) {
181
218
  });
182
219
  return;
183
220
  }
184
- for (const d of outcome.result.diagnostics) {
221
+ const { diagnostics } = !suppression && options && Object.keys(options).length ? outcome.checkWith(id, options) : outcome.result;
222
+ for (const d of diagnostics) {
185
223
  if (d.ruleId !== id) continue;
186
224
  const edits = d.fix ?? [];
187
225
  const fix = (fixer) => edits.map((e) => fixer.replaceTextRange(e.range, e.text));
@@ -209,6 +247,7 @@ function createRule(id, description) {
209
247
  };
210
248
  }
211
249
  const rules = Object.fromEntries(RULES.map((rule) => [rule.id, createRule(rule.id, rule.description)]));
250
+ rules[SUPPRESSION] = createRule(SUPPRESSION, "Suppression comments must name rules that exist.");
212
251
  const plugin = {
213
252
  meta: {
214
253
  name: NAME,
@@ -218,14 +257,17 @@ const plugin = {
218
257
  configs: {}
219
258
  };
220
259
  /**
221
- * Every rule at its default severity, for `.tsx` and `.jsx` files other than tests and stories,
222
- * which `check` also leaves out unless asked (`--include-tests`).
260
+ * Every rule at its default severity, and `suppression` as a warning, for `.tsx` and `.jsx` files
261
+ * other than tests and stories, which `check` also leaves out unless asked (`--include-tests`).
223
262
  */
224
263
  plugin.configs.recommended = {
225
264
  name: "onsystem/recommended",
226
265
  files: ["**/*.{tsx,jsx}"],
227
266
  ignores: [DEFAULT_TEST_EXCLUDE, DEFAULT_TEST_FOLDERS],
228
267
  plugins: { onsystem: plugin },
229
- rules: Object.fromEntries(RULES.map((rule) => [`onsystem/${rule.id}`, DEFAULT_SEVERITY[rule.id]]))
268
+ rules: {
269
+ ...Object.fromEntries(RULES.map((rule) => [`onsystem/${rule.id}`, DEFAULT_SEVERITY[rule.id]])),
270
+ [`onsystem/${SUPPRESSION}`]: "warn"
271
+ }
230
272
  };
231
273
  export { plugin as default };
package/dist/index.d.ts CHANGED
@@ -157,7 +157,9 @@ export declare function loadTokens(root: string, sources: TokenSource[]): Promis
157
157
  /**
158
158
  * DTCG files and the CSS generated from them often describe the same tokens.
159
159
  * Merge entries that share a custom property: keep the DTCG name and
160
- * description, and add the Tailwind mapping from CSS.
160
+ * description, and add the Tailwind mapping from CSS. A token without one (a
161
+ * `@theme inline` value, `rounded-sm`) merges with the token of its name and
162
+ * Tailwind key, such as the one a DTCG file exported from the stylesheet has.
161
163
  */
162
164
  export declare function mergeTokens(tokens: Token[]): Token[];
163
165
  interface ColorCandidate {
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { F as loadTokens, G as parseDoc, H as parseDesignMd, I as mergeTokens, K as extractComponents, L as toDesignMd, N as RULES, P as TokenIndex, R as toDtcg, U as parseCssTokens, V as parseDtcgTokens, W as attachDocs, _ as formatDiagnostics, a as fingerprint, at as defineConfig, ct as loadConfig, f as searchComponents, ft as findVariantDefinitions, it as configJsonSchema, m as checkSource, n as DesignSystemHost, o as loadDesignSystem, p as applyFixes, r as buildModel, rt as RULE_IDS, t as DesignSystem, y as VERSION } from "./design-system-7wZoJsMm.js";
2
- import { n as INSTRUCTIONS, r as createServer, t as serveStdio } from "./stdio-B31hp2UZ.js";
1
+ import { F as loadTokens, G as parseDoc, H as parseDesignMd, I as mergeTokens, K as extractComponents, L as toDesignMd, N as RULES, P as TokenIndex, R as toDtcg, U as parseCssTokens, V as parseDtcgTokens, W as attachDocs, _ as formatDiagnostics, a as fingerprint, at as defineConfig, ct as loadConfig, f as searchComponents, ft as findVariantDefinitions, it as configJsonSchema, m as checkSource, n as DesignSystemHost, o as loadDesignSystem, p as applyFixes, r as buildModel, rt as RULE_IDS, t as DesignSystem, y as VERSION } from "./design-system-Csqts7Eq.js";
2
+ import { n as INSTRUCTIONS, r as createServer, t as serveStdio } from "./stdio-0TpyqZaD.js";
3
3
  export { DesignSystem, DesignSystemHost, INSTRUCTIONS, RULES, RULE_IDS, TokenIndex, VERSION, applyFixes, attachDocs, buildModel, checkSource, configJsonSchema, createServer, defineConfig, extractComponents, findVariantDefinitions, fingerprint, formatDiagnostics, loadConfig, loadDesignSystem, loadTokens, mergeTokens, parseCssTokens, parseDesignMd, parseDoc, parseDtcgTokens, searchComponents, serveStdio, toDesignMd, toDtcg };
@@ -1,5 +1,5 @@
1
- import { At as relativePath, Ct as isWorkspaceRoot, Dt as isInside, Et as SCRIPT_FILE, Ft as truncate, It as unique, J as ConfigError, Lt as defaultProjectConfig, Mt as closest, Nt as json, Pt as plural, X as DEFAULT_SEVERITY, bt as detectProject, ct as loadConfig, d as descriptionOf, dt as formatPx, f as searchComponents, gt as notReadYet, h as uncheckedNotice, ht as libraryTitle, kt as pathOf, mt as LIBRARY_IDS, n as DesignSystemHost, ot as excludedBy, pt as LIBRARIES, q as CONFIG_FILES, r as buildModel, rt as RULE_IDS, tt as LEGACY_CONFIG_FILES, u as buildSearchIndex, v as NAME, xt as findDesignSystemPackage, y as VERSION, z as TOKEN_CATEGORIES } from "./design-system-7wZoJsMm.js";
2
- import { i as projectDirOf, n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-C5pMS6Xv.js";
1
+ import { At as relativePath, Ct as isWorkspaceRoot, Dt as isInside, Et as SCRIPT_FILE, Ft as truncate, It as unique, J as ConfigError, Lt as defaultProjectConfig, Mt as closest, Nt as json, Pt as plural, X as DEFAULT_SEVERITY, bt as detectProject, ct as loadConfig, d as descriptionOf, dt as formatPx, f as searchComponents, gt as notReadYet, h as uncheckedNotice, ht as libraryTitle, kt as pathOf, mt as LIBRARY_IDS, n as DesignSystemHost, ot as excludedBy, pt as LIBRARIES, q as CONFIG_FILES, r as buildModel, rt as RULE_IDS, tt as LEGACY_CONFIG_FILES, u as buildSearchIndex, v as NAME, xt as findDesignSystemPackage, y as VERSION, z as TOKEN_CATEGORIES } from "./design-system-Csqts7Eq.js";
2
+ import { i as projectDirOf, n as hasProjectMarker, r as loadTarget, t as findProjectRoot } from "./workspace-Dbxubo9n.js";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -1,4 +1,4 @@
1
- import { At as relativePath, Ct as isWorkspaceRoot, Dt as isInside, Tt as workspacePatterns, ct as loadConfig, i as componentFiles, lt as loadConfigWith, nt as PROJECT_MARKERS, st as hasOwnConfig, vt as PackageFinder, wt as workspacePackageDirs, xt as findDesignSystemPackage } from "./design-system-7wZoJsMm.js";
1
+ import { At as relativePath, Ct as isWorkspaceRoot, Dt as isInside, Tt as workspacePatterns, ct as loadConfig, i as componentFiles, lt as loadConfigWith, nt as PROJECT_MARKERS, st as hasOwnConfig, vt as PackageFinder, wt as workspacePackageDirs, xt as findDesignSystemPackage } from "./design-system-Csqts7Eq.js";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import os from "node:os";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onsystem",
3
- "version": "0.10.3",
3
+ "version": "0.11.0",
4
4
  "mcpName": "io.github.dgesteves/onsystem",
5
5
  "description": "Keeps coding agents on your design system: catches palette colors, native elements and APIs your system lacks, at every edit and pull request, with the fix. Set it up once in your design-system package; every app in your monorepo gets it.",
6
6
  "keywords": [