@describe-me/core 0.5.0-next.0 → 0.5.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,7 +32,8 @@ The manifest types live in `@describe-me/core/types`, for tools that want to
32
32
  read `.describe-me/manifest.json` themselves. Stored snapshots point into
33
33
  `assets/` and `styles/` through `ASSET_URL_PREFIX` and `STYLE_URL_PREFIX`
34
34
  ([Output directory](https://github.com/grzehub/describe-me#output-directory) in
35
- the root README).
35
+ the root README). `@describe-me/core/css-references` lists the `url()` and
36
+ `@import` references in CSS text.
36
37
 
37
38
  See the [root README](https://github.com/grzehub/describe-me#readme) for the
38
39
  full picture.
@@ -0,0 +1,17 @@
1
+ /** A `url()` or `@import` target in CSS text. */
2
+ export interface CssReference {
3
+ /** The URL as written, without quotes or surrounding whitespace. */
4
+ url: string;
5
+ /** Offset of the URL's first character in the scanned text. */
6
+ start: number;
7
+ /** Offset just past the URL's last character. */
8
+ end: number;
9
+ /** `import` for an `@import` target, written as a string or as `url()`. */
10
+ kind: 'url' | 'import';
11
+ }
12
+ /**
13
+ * Every `url()` and `@import` target in CSS text, in source order, remote,
14
+ * `data:` and `#fragment` URLs included. Comments and other strings are
15
+ * skipped, so `content: "url(x)"` yields nothing.
16
+ */
17
+ export declare function cssReferences(cssText: string): CssReference[];
@@ -0,0 +1,129 @@
1
+ /** Where a scan step can start: a comment, a string, an escape, `@import` or `url(`. */
2
+ const TOKEN = /\/\*|["'\\]|@import|url\(/gi;
3
+ const NAME_CHARACTER = /[\w\u0080-\uffff-]/;
4
+ const WHITESPACE = /\s/;
5
+ function isNameCharacter(char) {
6
+ return char !== undefined && NAME_CHARACTER.test(char);
7
+ }
8
+ function isQuote(char) {
9
+ return char === '"' || char === "'";
10
+ }
11
+ /** The offset of the quote that closes the string opening at `open`. A newline ends a broken string. */
12
+ function closingQuote(css, open) {
13
+ let index = open + 1;
14
+ while (index < css.length) {
15
+ const char = css[index];
16
+ if (char === '\\') {
17
+ index += 2;
18
+ continue;
19
+ }
20
+ if (char === css[open] || char === '\n') {
21
+ return index;
22
+ }
23
+ index++;
24
+ }
25
+ return css.length;
26
+ }
27
+ /** The offset just past the comment opening at `open`. */
28
+ function commentEnd(css, open) {
29
+ const close = css.indexOf('*/', open + 2);
30
+ return close === -1 ? css.length : close + 2;
31
+ }
32
+ function skipWhitespaceAndComments(css, start) {
33
+ let index = start;
34
+ while (index < css.length) {
35
+ if (WHITESPACE.test(css[index])) {
36
+ index++;
37
+ }
38
+ else if (css.startsWith('/*', index)) {
39
+ index = commentEnd(css, index);
40
+ }
41
+ else {
42
+ break;
43
+ }
44
+ }
45
+ return index;
46
+ }
47
+ /** `url(` at `index` as a function of its own, not the tail of a longer name such as `my-url(`. */
48
+ function isUrlFunction(css, index) {
49
+ return css.slice(index, index + 4).toLowerCase() === 'url(' && !isNameCharacter(css[index - 1]);
50
+ }
51
+ function referenceOf(css, start, end, kind) {
52
+ if (end <= start) {
53
+ return null;
54
+ }
55
+ return { url: css.slice(start, end), start, end, kind };
56
+ }
57
+ /** The quoted string opening at `open`, as a reference. */
58
+ function scanString(css, open, kind) {
59
+ const close = closingQuote(css, open);
60
+ return { reference: referenceOf(css, open + 1, close, kind), next: close + 1 };
61
+ }
62
+ /**
63
+ * The target of the `url(` starting at `index`. An unquoted URL runs until the
64
+ * first `)`, because a Google Fonts `css2` URL contains semicolons.
65
+ */
66
+ function scanUrl(css, index, kind) {
67
+ let start = index + 'url('.length;
68
+ while (start < css.length && WHITESPACE.test(css[start])) {
69
+ start++;
70
+ }
71
+ if (isQuote(css[start])) {
72
+ return scanString(css, start, kind);
73
+ }
74
+ const close = css.indexOf(')', start);
75
+ if (close === -1) {
76
+ return { reference: null, next: start };
77
+ }
78
+ let end = close;
79
+ while (end > start && WHITESPACE.test(css[end - 1])) {
80
+ end--;
81
+ }
82
+ return { reference: referenceOf(css, start, end, kind), next: close + 1 };
83
+ }
84
+ /** The target of the `@import` starting at `index`: a string or a `url()`. */
85
+ function scanImport(css, index) {
86
+ const target = skipWhitespaceAndComments(css, index + '@import'.length);
87
+ if (isQuote(css[target])) {
88
+ return scanString(css, target, 'import');
89
+ }
90
+ if (isUrlFunction(css, target)) {
91
+ return scanUrl(css, target, 'import');
92
+ }
93
+ return { reference: null, next: target };
94
+ }
95
+ function scanToken(css, index, token) {
96
+ const skipped = (next) => ({ reference: null, next });
97
+ if (token === '/*') {
98
+ return skipped(commentEnd(css, index));
99
+ }
100
+ if (token === '\\') {
101
+ return skipped(index + 2);
102
+ }
103
+ if (isQuote(token)) {
104
+ return skipped(closingQuote(css, index) + 1);
105
+ }
106
+ if (token.startsWith('@')) {
107
+ return isNameCharacter(css[index + token.length]) ? skipped(index + 1) : scanImport(css, index);
108
+ }
109
+ return isUrlFunction(css, index) ? scanUrl(css, index, 'url') : skipped(index + 1);
110
+ }
111
+ /**
112
+ * Every `url()` and `@import` target in CSS text, in source order, remote,
113
+ * `data:` and `#fragment` URLs included. Comments and other strings are
114
+ * skipped, so `content: "url(x)"` yields nothing.
115
+ */
116
+ export function cssReferences(cssText) {
117
+ const references = [];
118
+ const token = new RegExp(TOKEN);
119
+ let match = token.exec(cssText);
120
+ while (match !== null) {
121
+ const scanned = scanToken(cssText, match.index, match[0]);
122
+ if (scanned.reference) {
123
+ references.push(scanned.reference);
124
+ }
125
+ token.lastIndex = scanned.next;
126
+ match = token.exec(cssText);
127
+ }
128
+ return references;
129
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './types.js';
2
+ export { cssReferences, type CssReference } from './css-references.js';
2
3
  export { describeComponentType } from './describe-component-type.js';
3
4
  export { elementLabel } from './element-label.js';
4
5
  export { manifestDiagnostics } from './manifest-diagnostics.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './types.js';
2
+ export { cssReferences } from './css-references.js';
2
3
  export { describeComponentType } from './describe-component-type.js';
3
4
  export { elementLabel } from './element-label.js';
4
5
  export { manifestDiagnostics } from './manifest-diagnostics.js';
package/dist/types.d.ts CHANGED
@@ -136,11 +136,17 @@ export interface Manifest {
136
136
  /** Keyed by component name, as reported by the framework adapter. */
137
137
  components: Record<string, ComponentDoc>;
138
138
  /**
139
- * Asset URLs the snapshots point at on the test page's origin that could not
140
- * be found in the project, as root-relative paths (`/lib/images/logo.svg`).
139
+ * Asset URLs the snapshots, the preview head or copied CSS files point at
140
+ * that could not be found in the project, as root-relative paths
141
+ * (`/lib/images/logo.svg`). Paths outside the root use the `/@fs/` form.
141
142
  * They will not load in the viewer. Absent in manifests written before 0.4.
142
143
  */
143
144
  assetsMissing?: string[];
145
+ /**
146
+ * The preview head HTML, with project files as `describe-me-asset:` URLs.
147
+ * Absent when `previewHead` is not set.
148
+ */
149
+ head?: string;
144
150
  }
145
151
  /** The name adapters fall back to when a component has no usable name. */
146
152
  export declare const ANONYMOUS_COMPONENT: "Anonymous";
@@ -156,6 +162,6 @@ export interface ManifestDiagnostics {
156
162
  name: string;
157
163
  tests: number;
158
164
  }[];
159
- /** Root-relative asset paths that will not load in the viewer. */
165
+ /** Asset paths that will not load in the viewer, root-relative or in the `/@fs/` form. */
160
166
  assetsMissing: string[];
161
167
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@describe-me/core",
3
- "version": "0.5.0-next.0",
3
+ "version": "0.5.0-next.1",
4
4
  "description": "Recorder and manifest types for describe-me: living component docs from your tests",
5
5
  "keywords": [
6
6
  "vitest",
@@ -38,6 +38,10 @@
38
38
  "./diagnostics": {
39
39
  "types": "./dist/manifest-diagnostics.d.ts",
40
40
  "default": "./dist/manifest-diagnostics.js"
41
+ },
42
+ "./css-references": {
43
+ "types": "./dist/css-references.d.ts",
44
+ "default": "./dist/css-references.js"
41
45
  }
42
46
  },
43
47
  "files": [