@tsrx/core 0.1.65 → 0.1.66

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.
Files changed (52) hide show
  1. package/README.md +26 -1
  2. package/package.json +9 -5
  3. package/src/analyze/css-analyze.js +44 -6
  4. package/src/analyze/index.js +14 -1
  5. package/src/analyze/style-analyze.js +467 -0
  6. package/src/analyze/validation.js +57 -0
  7. package/src/diagnostics.js +22 -0
  8. package/src/index.js +17 -0
  9. package/src/parse/style.js +97 -7
  10. package/src/plugin.js +145 -47
  11. package/src/scope.js +1 -1
  12. package/src/transform/jsx/index.js +94 -417
  13. package/src/transform/jsx/style-scopes.js +842 -0
  14. package/src/transform/scoping.js +129 -79
  15. package/src/transform/segments.js +16 -4
  16. package/src/transform/style-ref.js +74 -13
  17. package/src/transform/stylesheet.js +2 -1
  18. package/src/utils/is-reference.js +59 -0
  19. package/tests/fixtures/scoped-styles/README.md +71 -0
  20. package/tests/fixtures/scoped-styles/apply-forms.expected.json +18 -0
  21. package/tests/fixtures/scoped-styles/apply-forms.tsrx +69 -0
  22. package/tests/fixtures/scoped-styles/assigned-positions.expected.json +29 -0
  23. package/tests/fixtures/scoped-styles/assigned-positions.tsrx +90 -0
  24. package/tests/fixtures/scoped-styles/class-opt-in.expected.json +13 -0
  25. package/tests/fixtures/scoped-styles/class-opt-in.tsrx +39 -0
  26. package/tests/fixtures/scoped-styles/control-flow-else-if.expected.json +11 -0
  27. package/tests/fixtures/scoped-styles/control-flow-else-if.tsrx +26 -0
  28. package/tests/fixtures/scoped-styles/control-flow.expected.json +17 -0
  29. package/tests/fixtures/scoped-styles/control-flow.tsrx +102 -0
  30. package/tests/fixtures/scoped-styles/cross-module-apply.expected.json +14 -0
  31. package/tests/fixtures/scoped-styles/cross-module-apply.tsrx +48 -0
  32. package/tests/fixtures/scoped-styles/element-rooted-templates.expected.json +12 -0
  33. package/tests/fixtures/scoped-styles/element-rooted-templates.tsrx +33 -0
  34. package/tests/fixtures/scoped-styles/precedence.expected.json +11 -0
  35. package/tests/fixtures/scoped-styles/precedence.tsrx +45 -0
  36. package/tests/fixtures/scoped-styles/rfc-opening-example/panel.expected.json +12 -0
  37. package/tests/fixtures/scoped-styles/rfc-opening-example/panel.tsrx +46 -0
  38. package/tests/fixtures/scoped-styles/rfc-opening-example/theme.expected.json +9 -0
  39. package/tests/fixtures/scoped-styles/rfc-opening-example/theme.tsrx +24 -0
  40. package/tests/fixtures/scoped-styles/search-panel.expected.json +11 -0
  41. package/tests/fixtures/scoped-styles/search-panel.tsrx +48 -0
  42. package/tests/fixtures/scoped-styles/sibling-scope.expected.json +11 -0
  43. package/tests/fixtures/scoped-styles/sibling-scope.tsrx +44 -0
  44. package/tests/fixtures/scoped-styles/sibling-scopes.expected.json +12 -0
  45. package/tests/fixtures/scoped-styles/sibling-scopes.tsrx +51 -0
  46. package/tests/fixtures/scoped-styles/theme-composition.expected.json +14 -0
  47. package/tests/fixtures/scoped-styles/theme-composition.tsrx +45 -0
  48. package/tests/fixtures/scoped-styles/theme-diamond.expected.json +10 -0
  49. package/tests/fixtures/scoped-styles/theme-diamond.tsrx +18 -0
  50. package/tests/shared/scoped-styles-fixtures.js +67 -0
  51. package/tests/utils/fixtures/style-syntax.js +519 -0
  52. package/types/index.d.ts +85 -0
@@ -0,0 +1,44 @@
1
+ // A block styles the items beside it and everything below them; it never
2
+ // styles the element that contains it. The fragment's children list, the
3
+ // section's children list, and each branch's fragment are scopes of their
4
+ // own, and the `.status` rule of the section's scope is pruned because
5
+ // <section> is not a child of that list.
6
+ export function Status({ ready }: { ready: boolean }) @{
7
+ <>
8
+ <style>
9
+ .status {
10
+ --label: status;
11
+ }
12
+ </style>
13
+ <section class="status">
14
+ <style>
15
+ .title {
16
+ --label: title;
17
+ }
18
+ .status {
19
+ margin: 0;
20
+ }
21
+ </style>
22
+ <h2 class="title">{'Status'}</h2>
23
+ @if (ready) {
24
+ <>
25
+ <style>
26
+ .ok {
27
+ --label: ok;
28
+ }
29
+ </style>
30
+ <p class="ok">{'Ready'}</p>
31
+ </>
32
+ } @else {
33
+ <>
34
+ <style>
35
+ .wait {
36
+ --label: wait;
37
+ }
38
+ </style>
39
+ <p class="wait">{'Waiting'}</p>
40
+ </>
41
+ }
42
+ </section>
43
+ </>
44
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "elements": {
3
+ "s0 outer": ["outer"],
4
+ "s1 first": ["outer", "first"],
5
+ "s2 second": ["outer", "second"],
6
+ "s2a deep": ["outer", "second", "deep"],
7
+ "s3 third": ["outer", "third"]
8
+ },
9
+ "cssOrder": ["outer", "first", "second", "deep", "third"],
10
+ "pruned": [],
11
+ "classMaps": {}
12
+ }
@@ -0,0 +1,51 @@
1
+ // Sibling scopes emit in source order, each after its parent and before the
2
+ // next sibling; the outer block sits last in source and still emits first.
3
+ export function Siblings() @{
4
+ <>
5
+ @{
6
+ <>
7
+ <style>
8
+ .first {
9
+ --label: first;
10
+ }
11
+ </style>
12
+ <div class="s1 first">{'1'}</div>
13
+ </>
14
+ }
15
+ @{
16
+ <>
17
+ <style>
18
+ .second {
19
+ --label: second;
20
+ }
21
+ </style>
22
+ <div class="s2 second">@{
23
+ <>
24
+ <style>
25
+ .deep {
26
+ --label: deep;
27
+ }
28
+ </style>
29
+ <span class="s2a deep">{'2a'}</span>
30
+ </>
31
+ }</div>
32
+ </>
33
+ }
34
+ @{
35
+ <>
36
+ <style>
37
+ .third {
38
+ --label: third;
39
+ }
40
+ </style>
41
+ <div class="s3 third">{'3'}</div>
42
+ </>
43
+ }
44
+ <style>
45
+ .outer {
46
+ --label: outer;
47
+ }
48
+ </style>
49
+ <div class="s0 outer">{'0'}</div>
50
+ </>
51
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "elements": {
3
+ "c1": ["base", "accent", "theme"]
4
+ },
5
+ "cssOrder": ["base", "accent", "theme", "plain"],
6
+ "pruned": [],
7
+ "classMaps": {
8
+ "base": ["base"],
9
+ "accent": ["base", "accent"],
10
+ "theme": ["base", "accent", "theme"],
11
+ "plain": ["plain"],
12
+ "bundle": ["plain", "base", "accent", "theme"]
13
+ }
14
+ }
@@ -0,0 +1,45 @@
1
+ // Same-module composition chain: every hash is known statically and inlined.
2
+ const base = <style>
3
+ .base {
4
+ --label: base;
5
+ }
6
+ div {
7
+ font-family: system-ui;
8
+ }
9
+ </style>;
10
+
11
+ const accent = <style apply={base}>
12
+ .accent {
13
+ --label: accent;
14
+ }
15
+ div {
16
+ color: blue;
17
+ }
18
+ </style>;
19
+
20
+ export const theme = <style apply={accent}>
21
+ .theme {
22
+ --label: theme;
23
+ }
24
+ div {
25
+ color: green;
26
+ }
27
+ </style>;
28
+
29
+ const plain = <style>
30
+ .plain {
31
+ --label: plain;
32
+ }
33
+ p {
34
+ margin: 0;
35
+ }
36
+ </style>;
37
+
38
+ export const bundle = <style apply={[plain, theme]} />;
39
+
40
+ export function Composed() @{
41
+ <>
42
+ <style apply={theme} />
43
+ <div class="c1">{'composed'}</div>
44
+ </>
45
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "elements": {},
3
+ "cssOrder": ["base", "accent", "pair"],
4
+ "pruned": [],
5
+ "classMaps": {
6
+ "base": ["base"],
7
+ "accent": ["base", "accent"],
8
+ "pair": ["base", "accent", "pair"]
9
+ }
10
+ }
@@ -0,0 +1,18 @@
1
+ // A diamond: `pair` applies `base` directly and again through `accent`.
2
+ const base = <style>
3
+ .base {
4
+ --label: base;
5
+ }
6
+ </style>;
7
+
8
+ const accent = <style apply={base}>
9
+ .accent {
10
+ --label: accent;
11
+ }
12
+ </style>;
13
+
14
+ export const pair = <style apply={[base, accent]}>
15
+ .pair {
16
+ --label: pair;
17
+ }
18
+ </style>;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Loader for the scoped-style conformance fixtures (RFC tsrx-org/RFCs#1) so
3
+ * consumer compilers run them straight from `@tsrx/core` instead of vendoring
4
+ * copies: `import { load_scoped_styles_fixtures } from
5
+ * '@tsrx/core/test-harness/scoped-styles-fixtures'`. The parser spec table is
6
+ * exposed beside it as `@tsrx/core/test-harness/style-syntax`.
7
+ *
8
+ * See `../fixtures/scoped-styles/README.md` for the expectation schema.
9
+ */
10
+
11
+ import { readdirSync, readFileSync } from 'node:fs';
12
+ import { dirname, join, relative } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ export const SCOPED_STYLES_FIXTURES_DIR = join(
16
+ dirname(fileURLToPath(import.meta.url)),
17
+ '../fixtures/scoped-styles',
18
+ );
19
+
20
+ /**
21
+ * @typedef {{
22
+ * name: string,
23
+ * path: string,
24
+ * source: string,
25
+ * expected: {
26
+ * elements: Record<string, string[]>,
27
+ * cssOrder: string[],
28
+ * pruned: string[],
29
+ * classMaps: Record<string, string[]>,
30
+ * knownFailure?: string,
31
+ * },
32
+ * }} ScopedStylesFixture
33
+ */
34
+
35
+ /**
36
+ * Every `<name>.tsrx` under the fixture directory (recursively) with its
37
+ * sibling `<name>.expected.json`, sorted by relative path.
38
+ *
39
+ * @param {string} [dir]
40
+ * @returns {ScopedStylesFixture[]}
41
+ */
42
+ export function load_scoped_styles_fixtures(dir = SCOPED_STYLES_FIXTURES_DIR) {
43
+ /** @type {ScopedStylesFixture[]} */
44
+ const fixtures = [];
45
+ /** @param {string} directory */
46
+ const visit = (directory) => {
47
+ for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) =>
48
+ a.name.localeCompare(b.name),
49
+ )) {
50
+ const path = join(directory, entry.name);
51
+ if (entry.isDirectory()) {
52
+ visit(path);
53
+ continue;
54
+ }
55
+ if (!entry.name.endsWith('.tsrx')) continue;
56
+ const expected_path = path.replace(/\.tsrx$/, '.expected.json');
57
+ fixtures.push({
58
+ name: relative(dir, path).replace(/\.tsrx$/, ''),
59
+ path,
60
+ source: readFileSync(path, 'utf8'),
61
+ expected: JSON.parse(readFileSync(expected_path, 'utf8')),
62
+ });
63
+ }
64
+ };
65
+ visit(dir);
66
+ return fixtures;
67
+ }