react-cheminfo 0.7.0 → 0.9.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 +60 -2
- package/bin/check-tokens.mjs +105 -0
- package/lib/about/core/about.d.ts +95 -0
- package/lib/about/core/about.d.ts.map +1 -0
- package/lib/about/core/about.js +78 -0
- package/lib/about/core/about.js.map +1 -0
- package/lib/about/core/index.d.ts +3 -0
- package/lib/about/core/index.d.ts.map +1 -0
- package/lib/about/core/index.js +2 -0
- package/lib/about/core/index.js.map +1 -0
- package/lib/about/ui/AboutCitations.d.ts +14 -0
- package/lib/about/ui/AboutCitations.d.ts.map +1 -0
- package/lib/about/ui/AboutCitations.js +40 -0
- package/lib/about/ui/AboutCitations.js.map +1 -0
- package/lib/about/ui/AboutPage.d.ts +32 -0
- package/lib/about/ui/AboutPage.d.ts.map +1 -0
- package/lib/about/ui/AboutPage.js +70 -0
- package/lib/about/ui/AboutPage.js.map +1 -0
- package/lib/about/ui/AboutSection.d.ts +23 -0
- package/lib/about/ui/AboutSection.d.ts.map +1 -0
- package/lib/about/ui/AboutSection.js +28 -0
- package/lib/about/ui/AboutSection.js.map +1 -0
- package/lib/about/ui/index.d.ts +7 -0
- package/lib/about/ui/index.d.ts.map +1 -0
- package/lib/about/ui/index.js +4 -0
- package/lib/about/ui/index.js.map +1 -0
- package/lib/chrome/ui/index.d.ts +1 -0
- package/lib/chrome/ui/index.d.ts.map +1 -1
- package/lib/chrome/ui/index.js +3 -0
- package/lib/chrome/ui/index.js.map +1 -1
- package/lib/citation/core/index.d.ts +1 -0
- package/lib/citation/core/index.d.ts.map +1 -1
- package/lib/citation/core/index.js +1 -0
- package/lib/citation/core/index.js.map +1 -1
- package/lib/citation/core/teachingPaper.d.ts +15 -0
- package/lib/citation/core/teachingPaper.d.ts.map +1 -0
- package/lib/citation/core/teachingPaper.js +32 -0
- package/lib/citation/core/teachingPaper.js.map +1 -0
- package/lib/clipboard/ui/CodeBlock.d.ts.map +1 -1
- package/lib/clipboard/ui/CodeBlock.js +4 -1
- package/lib/clipboard/ui/CodeBlock.js.map +1 -1
- package/lib/color/ui/ColorScaleLegend.js +1 -1
- package/lib/color/ui/ColorScaleLegend.js.map +1 -1
- package/lib/core.d.ts +3 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +5 -0
- package/lib/core.js.map +1 -1
- package/lib/credits/core/credits.d.ts +36 -0
- package/lib/credits/core/credits.d.ts.map +1 -1
- package/lib/credits/core/credits.js +42 -0
- package/lib/credits/core/credits.js.map +1 -1
- package/lib/credits/ui/CreditsList.js +1 -1
- package/lib/credits/ui/CreditsList.js.map +1 -1
- package/lib/delimited/ui/DelimitedTextPanel.js +1 -1
- package/lib/delimited/ui/DelimitedTextPanel.js.map +1 -1
- package/lib/ecosystem/core/sites.d.ts +1 -1
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +10 -0
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/core/tokens.d.ts.map +1 -1
- package/lib/ecosystem/core/tokens.js +1 -0
- package/lib/ecosystem/core/tokens.js.map +1 -1
- package/lib/ecosystem/ui/EcosystemLinks.js +2 -2
- package/lib/ecosystem/ui/EcosystemLinks.js.map +1 -1
- package/lib/ecosystem/ui/EcosystemMenu.js +1 -1
- package/lib/ecosystem/ui/EcosystemMenu.js.map +1 -1
- package/lib/ecosystem/ui/SiteTile.d.ts.map +1 -1
- package/lib/ecosystem/ui/SiteTile.js +5 -5
- package/lib/ecosystem/ui/SiteTile.js.map +1 -1
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +3 -0
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/orbital/core/index.d.ts +1 -1
- package/lib/orbital/core/index.d.ts.map +1 -1
- package/lib/orbital/core/index.js +1 -1
- package/lib/orbital/core/index.js.map +1 -1
- package/lib/orbital/core/realHarmonics.d.ts +5 -2
- package/lib/orbital/core/realHarmonics.d.ts.map +1 -1
- package/lib/orbital/core/realHarmonics.js.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts +18 -0
- package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalCanvas.js +84 -7
- package/lib/orbital/ui/AtomicOrbitalCanvas.js.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalViewer.d.ts +13 -0
- package/lib/orbital/ui/AtomicOrbitalViewer.d.ts.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalViewer.js +1 -1
- package/lib/orbital/ui/AtomicOrbitalViewer.js.map +1 -1
- package/lib/orbital/ui/camera.d.ts +15 -0
- package/lib/orbital/ui/camera.d.ts.map +1 -1
- package/lib/orbital/ui/camera.js +68 -0
- package/lib/orbital/ui/camera.js.map +1 -1
- package/lib/orbital/ui/renderVolume.d.ts.map +1 -1
- package/lib/orbital/ui/renderVolume.js +8 -1
- package/lib/orbital/ui/renderVolume.js.map +1 -1
- package/lib/orbital/ui/serial.d.ts +25 -0
- package/lib/orbital/ui/serial.d.ts.map +1 -0
- package/lib/orbital/ui/serial.js +25 -0
- package/lib/orbital/ui/serial.js.map +1 -0
- package/lib/orbital/ui/viewer.d.ts +23 -0
- package/lib/orbital/ui/viewer.d.ts.map +1 -1
- package/lib/orbital/ui/viewer.js +70 -17
- package/lib/orbital/ui/viewer.js.map +1 -1
- package/lib/pedagogy/ui/ExerciseProgressHeader.js +1 -1
- package/lib/pedagogy/ui/ExerciseProgressHeader.js.map +1 -1
- package/lib/pedagogy/ui/GlossaryText.js +2 -2
- package/lib/pedagogy/ui/GlossaryText.js.map +1 -1
- package/lib/pedagogy/ui/ReferenceSectionBlock.js +5 -2
- package/lib/pedagogy/ui/ReferenceSectionBlock.js.map +1 -1
- package/lib/pedagogy/ui/SyntaxTooltip.js +2 -2
- package/lib/pedagogy/ui/SyntaxTooltip.js.map +1 -1
- package/lib/pedagogy/ui/TutorialStepStrip.d.ts +15 -2
- package/lib/pedagogy/ui/TutorialStepStrip.d.ts.map +1 -1
- package/lib/pedagogy/ui/TutorialStepStrip.js +19 -5
- package/lib/pedagogy/ui/TutorialStepStrip.js.map +1 -1
- package/lib/pedagogy/ui/index.d.ts +1 -1
- package/lib/pedagogy/ui/index.d.ts.map +1 -1
- package/lib/pedagogy/ui/index.js.map +1 -1
- package/lib/periodic/ui/CategoryLegend.js +1 -1
- package/lib/periodic/ui/CategoryLegend.js.map +1 -1
- package/lib/periodic/ui/PeriodicTableChrome.js +2 -2
- package/lib/periodic/ui/PeriodicTableChrome.js.map +1 -1
- package/lib/seo/vite/ogCard.d.ts.map +1 -1
- package/lib/seo/vite/ogCard.js +1 -0
- package/lib/seo/vite/ogCard.js.map +1 -1
- package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
- package/lib/share/ui/ShareDialogContent.js +5 -2
- package/lib/share/ui/ShareDialogContent.js.map +1 -1
- package/lib/share/ui/SharePartOptions.js +1 -1
- package/lib/share/ui/SharePartOptions.js.map +1 -1
- package/lib/slides/core/demoLinks.d.ts +43 -0
- package/lib/slides/core/demoLinks.d.ts.map +1 -0
- package/lib/slides/core/demoLinks.js +104 -0
- package/lib/slides/core/demoLinks.js.map +1 -0
- package/lib/slides/core/index.d.ts +13 -0
- package/lib/slides/core/index.d.ts.map +1 -0
- package/lib/slides/core/index.js +7 -0
- package/lib/slides/core/index.js.map +1 -0
- package/lib/slides/core/sectionHeading.d.ts +19 -0
- package/lib/slides/core/sectionHeading.d.ts.map +1 -0
- package/lib/slides/core/sectionHeading.js +19 -0
- package/lib/slides/core/sectionHeading.js.map +1 -0
- package/lib/slides/core/slideNavigation.d.ts +38 -0
- package/lib/slides/core/slideNavigation.d.ts.map +1 -0
- package/lib/slides/core/slideNavigation.js +65 -0
- package/lib/slides/core/slideNavigation.js.map +1 -0
- package/lib/slides/core/talk.d.ts +82 -0
- package/lib/slides/core/talk.d.ts.map +1 -0
- package/lib/slides/core/talk.js +134 -0
- package/lib/slides/core/talk.js.map +1 -0
- package/lib/slides/core/talkManifest.d.ts +90 -0
- package/lib/slides/core/talkManifest.d.ts.map +1 -0
- package/lib/slides/core/talkManifest.js +109 -0
- package/lib/slides/core/talkManifest.js.map +1 -0
- package/lib/slides/core/talkOrigin.d.ts +50 -0
- package/lib/slides/core/talkOrigin.d.ts.map +1 -0
- package/lib/slides/core/talkOrigin.js +62 -0
- package/lib/slides/core/talkOrigin.js.map +1 -0
- package/lib/slides/ui/BackToSlides.d.ts +26 -0
- package/lib/slides/ui/BackToSlides.d.ts.map +1 -0
- package/lib/slides/ui/BackToSlides.js +24 -0
- package/lib/slides/ui/BackToSlides.js.map +1 -0
- package/lib/slides/ui/DemoLink.d.ts +42 -0
- package/lib/slides/ui/DemoLink.d.ts.map +1 -0
- package/lib/slides/ui/DemoLink.js +17 -0
- package/lib/slides/ui/DemoLink.js.map +1 -0
- package/lib/slides/ui/EmbedSlide.d.ts +19 -0
- package/lib/slides/ui/EmbedSlide.d.ts.map +1 -0
- package/lib/slides/ui/EmbedSlide.js +56 -0
- package/lib/slides/ui/EmbedSlide.js.map +1 -0
- package/lib/slides/ui/SectionSlide.d.ts +18 -0
- package/lib/slides/ui/SectionSlide.d.ts.map +1 -0
- package/lib/slides/ui/SectionSlide.js +18 -0
- package/lib/slides/ui/SectionSlide.js.map +1 -0
- package/lib/slides/ui/Slide.d.ts +52 -0
- package/lib/slides/ui/Slide.d.ts.map +1 -0
- package/lib/slides/ui/Slide.js +58 -0
- package/lib/slides/ui/Slide.js.map +1 -0
- package/lib/slides/ui/SlideBody.d.ts +32 -0
- package/lib/slides/ui/SlideBody.d.ts.map +1 -0
- package/lib/slides/ui/SlideBody.js +27 -0
- package/lib/slides/ui/SlideBody.js.map +1 -0
- package/lib/slides/ui/Slideshow.d.ts +93 -0
- package/lib/slides/ui/Slideshow.d.ts.map +1 -0
- package/lib/slides/ui/Slideshow.js +122 -0
- package/lib/slides/ui/Slideshow.js.map +1 -0
- package/lib/slides/ui/SlideshowBar.d.ts +33 -0
- package/lib/slides/ui/SlideshowBar.d.ts.map +1 -0
- package/lib/slides/ui/SlideshowBar.js +20 -0
- package/lib/slides/ui/SlideshowBar.js.map +1 -0
- package/lib/slides/ui/TalkList.d.ts +37 -0
- package/lib/slides/ui/TalkList.d.ts.map +1 -0
- package/lib/slides/ui/TalkList.js +68 -0
- package/lib/slides/ui/TalkList.js.map +1 -0
- package/lib/slides/ui/index.d.ts +19 -0
- package/lib/slides/ui/index.d.ts.map +1 -0
- package/lib/slides/ui/index.js +10 -0
- package/lib/slides/ui/index.js.map +1 -0
- package/lib/slides/vite/index.d.ts +3 -0
- package/lib/slides/vite/index.d.ts.map +1 -0
- package/lib/slides/vite/index.js +2 -0
- package/lib/slides/vite/index.js.map +1 -0
- package/lib/slides/vite/talks.d.ts +50 -0
- package/lib/slides/vite/talks.d.ts.map +1 -0
- package/lib/slides/vite/talks.js +87 -0
- package/lib/slides/vite/talks.js.map +1 -0
- package/lib/slides.d.ts +3 -0
- package/lib/slides.d.ts.map +1 -0
- package/lib/slides.js +3 -0
- package/lib/slides.js.map +1 -0
- package/lib/structure/ui/StructureEditor.js +2 -2
- package/lib/structure/ui/StructureEditor.js.map +1 -1
- package/lib/structure/ui/StructurePlaceholder.js +1 -1
- package/lib/structure/ui/StructurePlaceholder.js.map +1 -1
- package/lib/tokens/core/checkTokens.d.ts +51 -0
- package/lib/tokens/core/checkTokens.d.ts.map +1 -0
- package/lib/tokens/core/checkTokens.js +188 -0
- package/lib/tokens/core/checkTokens.js.map +1 -0
- package/lib/tokens/core/colors.d.ts +42 -0
- package/lib/tokens/core/colors.d.ts.map +1 -0
- package/lib/tokens/core/colors.js +119 -0
- package/lib/tokens/core/colors.js.map +1 -0
- package/lib/tokens/core/index.d.ts +3 -0
- package/lib/tokens/core/index.d.ts.map +1 -0
- package/lib/tokens/core/index.js +2 -0
- package/lib/tokens/core/index.js.map +1 -0
- package/lib/tokens/core/rules.d.ts +23 -0
- package/lib/tokens/core/rules.d.ts.map +1 -0
- package/lib/tokens/core/rules.js +90 -0
- package/lib/tokens/core/rules.js.map +1 -0
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/lib/vite.d.ts +1 -0
- package/lib/vite.d.ts.map +1 -1
- package/lib/vite.js +1 -0
- package/lib/vite.js.map +1 -1
- package/package.json +11 -3
- package/src/about/core/about.ts +176 -0
- package/src/about/core/index.ts +2 -0
- package/src/about/ui/AboutCitations.tsx +79 -0
- package/src/about/ui/AboutPage.tsx +177 -0
- package/src/about/ui/AboutSection.tsx +56 -0
- package/src/about/ui/index.ts +6 -0
- package/src/chrome/ui/index.ts +3 -0
- package/src/citation/core/index.ts +1 -0
- package/src/citation/core/teachingPaper.ts +36 -0
- package/src/clipboard/ui/CodeBlock.tsx +4 -1
- package/src/color/ui/ColorScaleLegend.tsx +1 -1
- package/src/core.ts +5 -0
- package/src/credits/core/credits.ts +42 -0
- package/src/credits/ui/CreditsList.tsx +1 -1
- package/src/delimited/ui/DelimitedTextPanel.tsx +1 -1
- package/src/ecosystem/core/sites.ts +12 -0
- package/src/ecosystem/core/tokens.ts +1 -0
- package/src/ecosystem/ui/EcosystemLinks.tsx +2 -2
- package/src/ecosystem/ui/EcosystemMenu.tsx +1 -1
- package/src/ecosystem/ui/SiteTile.tsx +7 -5
- package/src/ecosystem/ui/glyphs.tsx +40 -0
- package/src/orbital/core/index.ts +4 -1
- package/src/orbital/core/realHarmonics.ts +5 -2
- package/src/orbital/ui/AtomicOrbitalCanvas.tsx +126 -4
- package/src/orbital/ui/AtomicOrbitalViewer.tsx +14 -1
- package/src/orbital/ui/camera.ts +81 -0
- package/src/orbital/ui/renderVolume.ts +8 -1
- package/src/orbital/ui/serial.ts +35 -0
- package/src/orbital/ui/viewer.ts +72 -13
- package/src/pedagogy/ui/ExerciseProgressHeader.tsx +1 -1
- package/src/pedagogy/ui/GlossaryText.tsx +2 -2
- package/src/pedagogy/ui/ReferenceSectionBlock.tsx +5 -2
- package/src/pedagogy/ui/SyntaxTooltip.tsx +2 -2
- package/src/pedagogy/ui/TutorialStepStrip.tsx +39 -10
- package/src/pedagogy/ui/index.ts +4 -1
- package/src/periodic/ui/CategoryLegend.tsx +1 -1
- package/src/periodic/ui/PeriodicTableChrome.tsx +2 -2
- package/src/seo/vite/ogCard.ts +1 -0
- package/src/share/ui/ShareDialogContent.tsx +5 -2
- package/src/share/ui/SharePartOptions.tsx +1 -1
- package/src/slides/core/demoLinks.ts +113 -0
- package/src/slides/core/index.ts +22 -0
- package/src/slides/core/sectionHeading.ts +29 -0
- package/src/slides/core/slideNavigation.ts +77 -0
- package/src/slides/core/talk.ts +203 -0
- package/src/slides/core/talkManifest.ts +158 -0
- package/src/slides/core/talkOrigin.ts +83 -0
- package/src/slides/ui/BackToSlides.tsx +49 -0
- package/src/slides/ui/DemoLink.tsx +67 -0
- package/src/slides/ui/EmbedSlide.tsx +85 -0
- package/src/slides/ui/SectionSlide.tsx +44 -0
- package/src/slides/ui/Slide.tsx +143 -0
- package/src/slides/ui/SlideBody.tsx +64 -0
- package/src/slides/ui/Slideshow.tsx +282 -0
- package/src/slides/ui/SlideshowBar.tsx +83 -0
- package/src/slides/ui/TalkList.tsx +146 -0
- package/src/slides/ui/index.ts +18 -0
- package/src/slides/vite/index.ts +2 -0
- package/src/slides/vite/talks.ts +126 -0
- package/src/slides.ts +2 -0
- package/src/structure/ui/StructureEditor.tsx +2 -2
- package/src/structure/ui/StructurePlaceholder.tsx +1 -1
- package/src/tokens/core/checkTokens.ts +242 -0
- package/src/tokens/core/colors.ts +122 -0
- package/src/tokens/core/index.ts +6 -0
- package/src/tokens/core/rules.ts +103 -0
- package/src/ui.ts +1 -0
- package/src/vite.ts +1 -0
- package/styles/chrome.css +1 -0
- package/styles/slides.css +667 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// tokens-ok: file — the checker names the colours it looks for.
|
|
2
|
+
import { normalizeColor, readHexColor, readRgbColor } from './colors.ts';
|
|
3
|
+
import { BANNED_COLORS } from './rules.ts';
|
|
4
|
+
|
|
5
|
+
/** What a site got wrong where a token was called for. */
|
|
6
|
+
export type TokenViolationKind =
|
|
7
|
+
'blueprint-grey' | 'blueprint-blue' | 'retyped-token' | 'redeclared-brand';
|
|
8
|
+
|
|
9
|
+
/** One place a site writes a colour that is not its own to write. */
|
|
10
|
+
export interface TokenViolation {
|
|
11
|
+
/** The file it is written in, named as the caller named it. */
|
|
12
|
+
file: string;
|
|
13
|
+
/** Which line, counting from 1. */
|
|
14
|
+
line: number;
|
|
15
|
+
/** Which column, counting from 1. */
|
|
16
|
+
column: number;
|
|
17
|
+
/** What kind of mistake it is. */
|
|
18
|
+
kind: TokenViolationKind;
|
|
19
|
+
/** The text as written — the hex, the `rgb()` call, the property name. */
|
|
20
|
+
text: string;
|
|
21
|
+
/** The token to use instead, in one line. */
|
|
22
|
+
hint: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** How forgiving a scan is. */
|
|
26
|
+
export interface CheckTokensOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Colours the project legitimately owns — a subject-matter palette, a logo
|
|
29
|
+
* of somebody else.
|
|
30
|
+
* @default []
|
|
31
|
+
*/
|
|
32
|
+
allow?: readonly string[];
|
|
33
|
+
/**
|
|
34
|
+
* Lines carrying this marker are skipped, for the rare deliberate literal.
|
|
35
|
+
* @default 'tokens-ok'
|
|
36
|
+
*/
|
|
37
|
+
ignoreMarker?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Every colour a set of files writes that belongs to the family rather than to
|
|
42
|
+
* the site.
|
|
43
|
+
*
|
|
44
|
+
* The neutrals, the geometry and the type are shared, and the two colours a
|
|
45
|
+
* site owns come from `<SiteTheme siteId>`; a site that re-types either — a
|
|
46
|
+
* Blueprint grey because a component used it, `#f5f7fa` because that is what
|
|
47
|
+
* the token holds today, `--brand` because the palette was declared by hand —
|
|
48
|
+
* leaves its page unable to follow the family. Both spellings of one colour
|
|
49
|
+
* are caught, `#5f6b7c` and `rgb(95 107 124)` alike, and reading a token
|
|
50
|
+
* through `var()` is never a violation.
|
|
51
|
+
* @param files - The files to read, each with the path a report names it by.
|
|
52
|
+
* @param options - The colours the project owns, and the marker waiving a line.
|
|
53
|
+
* @returns Every violation, in file order and then in reading order.
|
|
54
|
+
*/
|
|
55
|
+
export function findTokenViolations(
|
|
56
|
+
files: ReadonlyArray<{ path: string; text: string }>,
|
|
57
|
+
options: CheckTokensOptions = {},
|
|
58
|
+
): TokenViolation[] {
|
|
59
|
+
const marker = (options.ignoreMarker ?? DEFAULT_IGNORE_MARKER).toLowerCase();
|
|
60
|
+
const allowed = new Set<string>();
|
|
61
|
+
for (const entry of options.allow ?? []) {
|
|
62
|
+
const color = normalizeColor(entry);
|
|
63
|
+
if (color !== null) allowed.add(color);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const violations: TokenViolation[] = [];
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
// A file whose opening lines carry the marker defines the palette rather
|
|
69
|
+
// than consuming it — the token block itself, the registry of banned
|
|
70
|
+
// colours, an image generated outside any page.
|
|
71
|
+
if (waivesWholeFile(file.text, marker)) continue;
|
|
72
|
+
const scan = scanFile(file.text, marker);
|
|
73
|
+
for (const candidate of scan.candidates) {
|
|
74
|
+
if (scan.markedLines.has(candidate.line)) continue;
|
|
75
|
+
if (candidate.color !== null && allowed.has(candidate.color)) continue;
|
|
76
|
+
violations.push({
|
|
77
|
+
file: file.path,
|
|
78
|
+
line: candidate.line,
|
|
79
|
+
column: candidate.column,
|
|
80
|
+
kind: candidate.kind,
|
|
81
|
+
text: candidate.text,
|
|
82
|
+
hint: candidate.hint,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return violations;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const DEFAULT_IGNORE_MARKER = 'tokens-ok';
|
|
90
|
+
|
|
91
|
+
// How far into a file the whole-file waiver may sit: a licence header, an
|
|
92
|
+
// import or two, and the comment saying why this file writes colours.
|
|
93
|
+
const WAIVER_LINES = 6;
|
|
94
|
+
|
|
95
|
+
function waivesWholeFile(text: string, marker: string): boolean {
|
|
96
|
+
if (marker.length === 0) return false;
|
|
97
|
+
const head = text.split('\n', WAIVER_LINES).join('\n').toLowerCase();
|
|
98
|
+
return head.includes(`${marker}: file`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Longest first, so `--brand-alt` is never read as `--brand`.
|
|
102
|
+
const BRAND_PROPERTIES = ['--brand-alt', '--brand', '--accent'] as const;
|
|
103
|
+
|
|
104
|
+
interface Candidate extends Omit<TokenViolation, 'file'> {
|
|
105
|
+
/** The colour written, or `null` for a property that should not be declared. */
|
|
106
|
+
color: string | null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface FileScan {
|
|
110
|
+
candidates: Candidate[];
|
|
111
|
+
markedLines: Set<number>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// One pass over the file: the marker, the block structure and every colour are
|
|
115
|
+
// all read from the same walk, so nothing is matched a second time per line.
|
|
116
|
+
function scanFile(text: string, marker: string): FileScan {
|
|
117
|
+
const lower = text.toLowerCase();
|
|
118
|
+
const candidates: Candidate[] = [];
|
|
119
|
+
const markedLines = new Set<number>();
|
|
120
|
+
const rootBlocks: boolean[] = [];
|
|
121
|
+
// Which parentheses are open, and whether each is a `var(` past its comma —
|
|
122
|
+
// a colour there is a fallback for the token named beside it.
|
|
123
|
+
const parens: Array<{ isVar: boolean; sawComma: boolean }> = [];
|
|
124
|
+
let selectorStart = 0;
|
|
125
|
+
let line = 1;
|
|
126
|
+
let lineStart = 0;
|
|
127
|
+
|
|
128
|
+
for (let index = 0; index < lower.length; index++) {
|
|
129
|
+
const char = lower[index];
|
|
130
|
+
if (char === '\n') {
|
|
131
|
+
line++;
|
|
132
|
+
lineStart = index + 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (
|
|
136
|
+
marker.length > 0 &&
|
|
137
|
+
char === marker[0] &&
|
|
138
|
+
lower.startsWith(marker, index)
|
|
139
|
+
) {
|
|
140
|
+
markedLines.add(line);
|
|
141
|
+
index += marker.length - 1;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (char === '(') {
|
|
145
|
+
parens.push({
|
|
146
|
+
isVar: lower.startsWith('var(', index - 3),
|
|
147
|
+
sawComma: false,
|
|
148
|
+
});
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (char === ')') {
|
|
152
|
+
parens.pop();
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (char === ',') {
|
|
156
|
+
const open = parens.at(-1);
|
|
157
|
+
if (open !== undefined) open.sawComma = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (char === '{') {
|
|
161
|
+
rootBlocks.push(lower.slice(selectorStart, index).includes(':root'));
|
|
162
|
+
selectorStart = index + 1;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (char === '}' || char === ';') {
|
|
166
|
+
if (char === '}') rootBlocks.pop();
|
|
167
|
+
selectorStart = index + 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (char === '#' || char === 'r') {
|
|
172
|
+
const match =
|
|
173
|
+
char === '#' ? readHexColor(lower, index) : readRgbColor(lower, index);
|
|
174
|
+
if (match === null) continue;
|
|
175
|
+
const rule = BANNED_COLORS.get(match.color);
|
|
176
|
+
const open = parens.at(-1);
|
|
177
|
+
const isFallback = open !== undefined && open.isVar && open.sawComma;
|
|
178
|
+
if (rule !== undefined && !isFallback) {
|
|
179
|
+
candidates.push({
|
|
180
|
+
line,
|
|
181
|
+
column: index - lineStart + 1,
|
|
182
|
+
kind: rule.kind,
|
|
183
|
+
text: text.slice(index, match.end),
|
|
184
|
+
hint: rule.hint,
|
|
185
|
+
color: match.color,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
index = match.end - 1;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (char === '-') {
|
|
193
|
+
const property = readBrandDeclaration(lower, index);
|
|
194
|
+
if (property === null) continue;
|
|
195
|
+
if (rootBlocks.includes(true)) {
|
|
196
|
+
candidates.push({
|
|
197
|
+
line,
|
|
198
|
+
column: index - lineStart + 1,
|
|
199
|
+
kind: 'redeclared-brand',
|
|
200
|
+
text: property.name,
|
|
201
|
+
hint: `render <SiteTheme siteId> instead of declaring ${property.name} in :root.`,
|
|
202
|
+
color: null,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
index = property.end - 1;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return { candidates, markedLines };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// A declaration of one of the palette properties. Reading one — `var(--brand)`
|
|
212
|
+
// — has no colon after the name, so it never lands here.
|
|
213
|
+
function readBrandDeclaration(
|
|
214
|
+
lower: string,
|
|
215
|
+
start: number,
|
|
216
|
+
): { name: string; end: number } | null {
|
|
217
|
+
for (const name of BRAND_PROPERTIES) {
|
|
218
|
+
if (!lower.startsWith(name, start)) continue;
|
|
219
|
+
const after = start + name.length;
|
|
220
|
+
// `--brand-alt-text` is its own property, not `--brand-alt`.
|
|
221
|
+
if (isNameChar(lower[after])) return null;
|
|
222
|
+
let index = after;
|
|
223
|
+
while (isSpace(lower[index])) index++;
|
|
224
|
+
if (lower[index] !== ':') return null;
|
|
225
|
+
return { name, end: after };
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isNameChar(char: string | undefined): boolean {
|
|
231
|
+
if (char === undefined) return false;
|
|
232
|
+
return (
|
|
233
|
+
(char >= '0' && char <= '9') ||
|
|
234
|
+
(char >= 'a' && char <= 'z') ||
|
|
235
|
+
char === '-' ||
|
|
236
|
+
char === '_'
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isSpace(char: string | undefined): boolean {
|
|
241
|
+
return char === ' ' || char === '\t';
|
|
242
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// tokens-ok: file — the colour reader is tested against literals.
|
|
2
|
+
/**
|
|
3
|
+
* Reading a colour out of source text, in both spellings the same value is
|
|
4
|
+
* written in: `#5f6b7c` and `rgb(95 107 124)`. Both come back as the same six
|
|
5
|
+
* lowercase hex digits, so a table of banned colours holds one entry per
|
|
6
|
+
* colour rather than one per way of typing it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** A colour found in the text, and where it ends. */
|
|
10
|
+
export interface ColorMatch {
|
|
11
|
+
/** The value, as six lowercase hex digits, without its `#`. */
|
|
12
|
+
color: string;
|
|
13
|
+
/** The index just past the last character the colour is written with. */
|
|
14
|
+
end: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The colour a hex literal starts at, in `#rgb`, `#rgba`, `#rrggbb` or
|
|
19
|
+
* `#rrggbbaa` form. The alpha, when there is one, says nothing about the
|
|
20
|
+
* colour and is dropped.
|
|
21
|
+
* @param lower - The text being scanned, already lowercased.
|
|
22
|
+
* @param start - The index of the `#`.
|
|
23
|
+
* @returns The colour and its end, or `null` when it is not a hex colour.
|
|
24
|
+
*/
|
|
25
|
+
export function readHexColor(lower: string, start: number): ColorMatch | null {
|
|
26
|
+
let end = start + 1;
|
|
27
|
+
while (isHexDigit(lower[end])) end++;
|
|
28
|
+
|
|
29
|
+
// A run of hex digits running straight into a word is an identifier — a
|
|
30
|
+
// private field, an anchor — rather than a colour.
|
|
31
|
+
if (isWordChar(lower[end])) return null;
|
|
32
|
+
|
|
33
|
+
const digits = end - start - 1;
|
|
34
|
+
if (digits === 3 || digits === 4) {
|
|
35
|
+
const red = lower[start + 1];
|
|
36
|
+
const green = lower[start + 2];
|
|
37
|
+
const blue = lower[start + 3];
|
|
38
|
+
return { color: `${red}${red}${green}${green}${blue}${blue}`, end };
|
|
39
|
+
}
|
|
40
|
+
if (digits === 6 || digits === 8) {
|
|
41
|
+
return { color: lower.slice(start + 1, start + 7), end };
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The colour an `rgb()` or `rgba()` call starts at, in either the comma or the
|
|
48
|
+
* space separated spelling.
|
|
49
|
+
*
|
|
50
|
+
* Only a plain byte triple is read: a percentage, a decimal or a variable
|
|
51
|
+
* inside the call means the value cannot be compared with a hex literal, and
|
|
52
|
+
* is left alone rather than guessed at.
|
|
53
|
+
* @param lower - The text being scanned, already lowercased.
|
|
54
|
+
* @param start - The index of the `r`.
|
|
55
|
+
* @returns The colour and its end, or `null` when it is not one.
|
|
56
|
+
*/
|
|
57
|
+
export function readRgbColor(lower: string, start: number): ColorMatch | null {
|
|
58
|
+
let open = -1;
|
|
59
|
+
if (lower.startsWith('rgb(', start)) open = start + 3;
|
|
60
|
+
else if (lower.startsWith('rgba(', start)) open = start + 4;
|
|
61
|
+
if (open === -1) return null;
|
|
62
|
+
|
|
63
|
+
const close = lower.indexOf(')', open);
|
|
64
|
+
if (close === -1) return null;
|
|
65
|
+
|
|
66
|
+
const components: number[] = [];
|
|
67
|
+
let value = -1;
|
|
68
|
+
for (let index = open + 1; index < close; index++) {
|
|
69
|
+
const char = lower[index];
|
|
70
|
+
if (char !== undefined && char >= '0' && char <= '9') {
|
|
71
|
+
value = Math.max(value, 0) * 10 + Number(char);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (value >= 0) {
|
|
75
|
+
components.push(value);
|
|
76
|
+
value = -1;
|
|
77
|
+
}
|
|
78
|
+
// The alpha of the modern syntax, which the colour does not depend on.
|
|
79
|
+
if (char === '/') break;
|
|
80
|
+
if (char !== ' ' && char !== ',' && char !== '\t' && char !== '\n') {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (value >= 0) components.push(value);
|
|
85
|
+
if (components.length < 3) return null;
|
|
86
|
+
|
|
87
|
+
let color = '';
|
|
88
|
+
for (let index = 0; index < 3; index++) {
|
|
89
|
+
const component = components[index];
|
|
90
|
+
if (component === undefined || component > 255) return null;
|
|
91
|
+
color += component.toString(16).padStart(2, '0');
|
|
92
|
+
}
|
|
93
|
+
return { color, end: close + 1 };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The `rrggbb` form of a colour written by hand — in an allow list, on a
|
|
98
|
+
* command line — in any of the spellings the scanner itself reads.
|
|
99
|
+
* @param value - The colour, with or without its `#`.
|
|
100
|
+
* @returns The six lowercase hex digits, or `null` when it is not a colour.
|
|
101
|
+
*/
|
|
102
|
+
export function normalizeColor(value: string): string | null {
|
|
103
|
+
const text = value.trim().toLowerCase();
|
|
104
|
+
if (text === '') return null;
|
|
105
|
+
let match: ColorMatch | null = null;
|
|
106
|
+
if (text.startsWith('#')) match = readHexColor(text, 0);
|
|
107
|
+
else if (text.startsWith('rgb')) match = readRgbColor(text, 0);
|
|
108
|
+
else match = readHexColor(`#${text}`, 0);
|
|
109
|
+
return match === null ? null : match.color;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isHexDigit(char: string | undefined): boolean {
|
|
113
|
+
if (char === undefined) return false;
|
|
114
|
+
return (char >= '0' && char <= '9') || (char >= 'a' && char <= 'f');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isWordChar(char: string | undefined): boolean {
|
|
118
|
+
if (char === undefined) return false;
|
|
119
|
+
return (
|
|
120
|
+
(char >= '0' && char <= '9') || (char >= 'a' && char <= 'z') || char === '_'
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// tokens-ok: file — the colours a site may not write are listed here.
|
|
2
|
+
import type { TokenViolationKind } from './checkTokens.ts';
|
|
3
|
+
|
|
4
|
+
/** What a colour a site may not write should have been. */
|
|
5
|
+
export interface ColorRule {
|
|
6
|
+
/** Which kind of violation writing it raises. */
|
|
7
|
+
kind: TokenViolationKind;
|
|
8
|
+
/** The token to write instead, in one line. */
|
|
9
|
+
hint: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The colours a site may not write, keyed by their six lowercase hex digits.
|
|
14
|
+
*
|
|
15
|
+
* Two things land here. Blueprint's own neutrals and intent blue, which arrive
|
|
16
|
+
* by reading a value off a component and pasting it: they are near the
|
|
17
|
+
* family's greys without being them, which is what makes two of our sites
|
|
18
|
+
* opened side by side read as two products. And the family's own token values
|
|
19
|
+
* re-typed as literals, which pins the page to the colour the token held on
|
|
20
|
+
* the day it was copied — a retune then moves everything except it.
|
|
21
|
+
*
|
|
22
|
+
* `#ffffff` is deliberately absent: `--surface` is white, and so are a hundred
|
|
23
|
+
* legitimate whites in a shadow, a gradient or a `color-mix`.
|
|
24
|
+
*/
|
|
25
|
+
export const BANNED_COLORS: ReadonlyMap<string, ColorRule> = new Map<
|
|
26
|
+
string,
|
|
27
|
+
ColorRule
|
|
28
|
+
>([
|
|
29
|
+
[
|
|
30
|
+
'f6f7f9',
|
|
31
|
+
{
|
|
32
|
+
kind: 'blueprint-grey',
|
|
33
|
+
hint: "Blueprint's page grey — the family's page is var(--surface-sunken).",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
[
|
|
37
|
+
'd3d8de',
|
|
38
|
+
{
|
|
39
|
+
kind: 'blueprint-grey',
|
|
40
|
+
hint: "Blueprint's light grey — the family's line is var(--border).",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
'5f6b7c',
|
|
45
|
+
{
|
|
46
|
+
kind: 'blueprint-grey',
|
|
47
|
+
hint: "Blueprint's muted text — use var(--text-muted).",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
'2d72d2',
|
|
52
|
+
{
|
|
53
|
+
kind: 'blueprint-blue',
|
|
54
|
+
hint: "Blueprint's intent blue — the site's own colour is var(--accent).",
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
[
|
|
58
|
+
'137cbd',
|
|
59
|
+
{
|
|
60
|
+
kind: 'blueprint-blue',
|
|
61
|
+
hint: "Blueprint's older intent blue — the site's own colour is var(--accent).",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
'f5f7fa',
|
|
66
|
+
{
|
|
67
|
+
kind: 'retyped-token',
|
|
68
|
+
hint: 'the value of --surface-sunken — use var(--surface-sunken).',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
'dfe3e8',
|
|
73
|
+
{
|
|
74
|
+
kind: 'retyped-token',
|
|
75
|
+
hint: 'the value of --border — use var(--border).',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
'c3cad3',
|
|
80
|
+
{
|
|
81
|
+
kind: 'retyped-token',
|
|
82
|
+
hint: 'the value of --border-strong — use var(--border-strong).',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
'16202c',
|
|
87
|
+
{ kind: 'retyped-token', hint: 'the value of --text — use var(--text).' },
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
'5b6875',
|
|
91
|
+
{
|
|
92
|
+
kind: 'retyped-token',
|
|
93
|
+
hint: 'the value of --text-muted — use var(--text-muted).',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
[
|
|
97
|
+
'8a96a3',
|
|
98
|
+
{
|
|
99
|
+
kind: 'retyped-token',
|
|
100
|
+
hint: 'the value of --text-faint — use var(--text-faint).',
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
]);
|
package/src/ui.ts
CHANGED
package/src/vite.ts
CHANGED
package/styles/chrome.css
CHANGED