@markuplint/types 5.0.0-rc.0 → 5.0.0-rc.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,33 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [5.0.0-rc.2](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.1...v5.0.0-rc.2) (2026-04-15)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **types:** allow empty string for BCP47 type (lang="" is valid per HTML LS) ([ceba672](https://github.com/markuplint/markuplint/commit/ceba6726be6f34200bf54a2808c3eb893f31a032))
11
+ - **types:** back-port Pattern type variant to specific-schema source ([6f7466f](https://github.com/markuplint/markuplint/commit/6f7466fc320fa69842f1f7e201a7b637baa58d1a))
12
+ - **types:** reject zero and negative srcset descriptors ([5584d20](https://github.com/markuplint/markuplint/commit/5584d2089fe1dfe508d43601fa14aeff4e08b265))
13
+ - **types:** use spec-verbatim regex for Email validator ([b517a49](https://github.com/markuplint/markuplint/commit/b517a4926ff213bbbc1eea27583c463b1917b760))
14
+
15
+ - feat(types)!: remove deprecated Token.getLine and Token.getCol static methods ([fad012c](https://github.com/markuplint/markuplint/commit/fad012c9f1f7e5bb48b79ef639d4cefa61123c1c))
16
+
17
+ ### Features
18
+
19
+ - **types:** add SimpleColor, Email, and DateTime subtype validators ([1687931](https://github.com/markuplint/markuplint/commit/1687931df058d334797135a925fdf738970ac25a)), closes [#3598](https://github.com/markuplint/markuplint/issues/3598)
20
+ - **types:** add SRIHash type for integrity attribute validation ([7672999](https://github.com/markuplint/markuplint/commit/7672999a17f7d96dc286aabfcea5cc0861b73be5))
21
+ - **types:** add SRIHash type for integrity attribute validation ([ea30c84](https://github.com/markuplint/markuplint/commit/ea30c84a87025ca5d7284ce5eaad89cc3c802d92))
22
+ - **types:** add URL validation with strict checks ([3e64e86](https://github.com/markuplint/markuplint/commit/3e64e86331f92b5914a4107407f9b25233188083))
23
+
24
+ ### BREAKING CHANGES
25
+
26
+ - Token.getLine() and Token.getCol() have been removed.
27
+ Use Token.getPosition() instead.
28
+
29
+ # [5.0.0-rc.1](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.0...v5.0.0-rc.1) (2026-03-27)
30
+
31
+ **Note:** Version bump only for package @markuplint/types
32
+
6
33
  # [5.0.0-rc.0](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.3...v5.0.0-rc.0) (2026-03-12)
7
34
 
8
35
  **Note:** Version bump only for package @markuplint/types
package/README.md CHANGED
@@ -82,6 +82,13 @@ The package also exports the following types:
82
82
  | `FunctionBody` | Event handler attributes | | 🚧 |
83
83
  | `Pattern` | `input[pattern]` | [WHATWG](https://html.spec.whatwg.org/multipage/input.html#compiled-pattern-regular-expression) | ✅ |
84
84
  | `DateTime` | `time[datetime]` and more | [WHATWG](https://html.spec.whatwg.org/multipage/text-level-semantics.html#datetime-value) | ✅ |
85
+ | `DateString` | `input[value]` when `type=date` | [WHATWG](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#dates) | ✅ |
86
+ | `TimeString` | `input[value]` when `type=time` | [WHATWG](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#times) | ✅ |
87
+ | `MonthString` | `input[value]` when `type=month` | [WHATWG](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#months) | ✅ |
88
+ | `WeekString` | `input[value]` when `type=week` | [WHATWG](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#weeks) | ✅ |
89
+ | `LocalDateTimeString` | `input[value]` when `type=datetime-local` | [WHATWG](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#local-dates-and-times) | ✅ |
90
+ | `SimpleColor` | `input[value]` when `type=color` | [WHATWG](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-simple-colour) | ✅ |
91
+ | `Email` | `input[value]` when `type=email` | [WHATWG](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address) | ✅ |
85
92
  | `TabIndex` | The `tabindex` attribute | [WHATWG](https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex) | ✅ |
86
93
  | `BCP47` | The `lang` attribute and more | [RFC](https://tools.ietf.org/rfc/bcp/bcp47.html) | ✅ |
87
94
  | `URL` | Some attributes | [WHATWG](https://html.spec.whatwg.org/multipage/urls-and-fetching.html#valid-url-potentially-surrounded-by-spaces) | ✅ |
@@ -6,14 +6,14 @@
6
6
 
7
7
  ## 型の共用体(Type Union)
8
8
 
9
- 型システムの中核は、`src/types.schema.ts` で定義される 5 つのメンバーから成る共用体型 `Type` です。
9
+ 型システムの中核は、`src/types.schema.ts` で定義される 6 つのメンバーから成る共用体型 `Type` です。
10
10
 
11
11
  ```ts
12
12
  // src/types.schema.ts
13
- export type Type = KeywordDefinedType | List | Enum | Number | Directive;
13
+ export type Type = KeywordDefinedType | List | Enum | Number | Directive | Pattern;
14
14
  ```
15
15
 
16
- markuplint のすべての属性値仕様は、最終的にこの 5 つの形式のいずれかに解決されます。`src/check-base.ts` のディスパッチャーが型の構造を判別し、対応するチェッカーに処理を振り分けます。
16
+ markuplint のすべての属性値仕様は、最終的にこの 6 つの形式のいずれかに解決されます。`src/check-base.ts` のディスパッチャーが型の構造を判別し、対応するチェッカーに処理を振り分けます。
17
17
 
18
18
  ```ts
19
19
  // src/check-base.ts
@@ -23,6 +23,7 @@ export function checkBase(value: string, type: ReadonlyDeep<Type>, defs: Defs, r
23
23
  if (isEnum(type)) return checkEnum(value, type, ref);
24
24
  if (isNumber(type)) return checkNumber(value, type, ref);
25
25
  if (isDirective(type)) return checkDirective(value, type, defs, ref, cache);
26
+ if (isPattern(type)) return checkPattern(value, type);
26
27
  throw new Error('Unknown type');
27
28
  }
28
29
  ```
@@ -36,6 +37,7 @@ export function checkBase(value: string, type: ReadonlyDeep<Type>, defs: Defs, r
36
37
  | **Enum** | `object` | `'enum' in type` | 許可された文字列値の固定集合 | `{ enum: ["auto", "ltr", "rtl"] }` |
37
38
  | **Number** | `object` | `type.type === 'float' \| 'integer'` | 範囲制約付きの数値 | `{ type: "integer", gte: 0 }` |
38
39
  | **Directive** | `object` | `'directive' in type` | セパレータで分割して個別検証する複合値 | `{ directive: [";"], token: "URL" }` |
40
+ | **Pattern** | `object` | `'pattern' in type` | 正規表現または完全一致による値検証 | `{ pattern: "/^[a-z]+$/i" }` |
39
41
 
40
42
  ### KeywordDefinedType
41
43
 
@@ -385,21 +387,28 @@ SourceSizeList: {
385
387
 
386
388
  `src/defs.ts` の `defs` オブジェクトには 30 以上の組み込み型が登録されています。代表的なものを以下に示します。
387
389
 
388
- | 型識別子 | 検証方式 | 仕様 |
389
- | ------------------- | ---------------------------- | ---------------------- |
390
- | `Any` | 常にマッチ | -- |
391
- | `NoEmptyAny` | 空文字列を拒否 | -- |
392
- | `Number` | 浮動小数点チェック | -- |
393
- | `Int` | 整数チェック | -- |
394
- | `Uint` | 非負整数チェック | -- |
395
- | `URL` | 常にマッチ(後述の注意参照) | WHATWG URL |
396
- | `DOMID` | 空白なし・空でない | HTML #id |
397
- | `DateTime` | 完全な日時パース | WHATWG datetime |
398
- | `BCP47` | RFC BCP 47 言語タグ | IETF BCP 47 |
399
- | `CustomElementName` | 有効なカスタム要素名 | WHATWG Custom Elements |
400
- | `MIMEType` | MIME タイプ解析 | MIME Sniffing |
401
- | `SourceSizeList` | CSS 構文ベース | HTML `<img sizes>` |
402
- | `AutoComplete` | 複雑なマルチトークン | HTML autocomplete |
390
+ | 型識別子 | 検証方式 | 仕様 |
391
+ | --------------------- | --------------------------------- | ---------------------- |
392
+ | `Any` | 常にマッチ | -- |
393
+ | `NoEmptyAny` | 空文字列を拒否 | -- |
394
+ | `Number` | 浮動小数点チェック | -- |
395
+ | `Int` | 整数チェック | -- |
396
+ | `Uint` | 非負整数チェック | -- |
397
+ | `URL` | 常にマッチ(後述の注意参照) | WHATWG URL |
398
+ | `DOMID` | 空白なし・空でない | HTML #id |
399
+ | `DateTime` | 完全な日時パース | WHATWG datetime |
400
+ | `DateString` | 日付のみ (`YYYY-MM-DD`) | WHATWG dates |
401
+ | `TimeString` | 時刻のみ (`hh:mm[:ss]`) | WHATWG times |
402
+ | `MonthString` | 月のみ (`YYYY-MM`) | WHATWG months |
403
+ | `WeekString` | 週のみ (`YYYY-Www`) | WHATWG weeks |
404
+ | `LocalDateTimeString` | ローカル日時 (`YYYY-MM-DDThh:mm`) | WHATWG local dates |
405
+ | `SimpleColor` | 単純色 (`#rrggbb`) | WHATWG simple colour |
406
+ | `Email` | メールアドレス (ASCII のみ) | HTML valid email |
407
+ | `BCP47` | RFC BCP 47 言語タグ | IETF BCP 47 |
408
+ | `CustomElementName` | 有効なカスタム要素名 | WHATWG Custom Elements |
409
+ | `MIMEType` | MIME タイプ解析 | MIME Sniffing |
410
+ | `SourceSizeList` | CSS 構文ベース | HTML `<img sizes>` |
411
+ | `AutoComplete` | 複雑なマルチトークン | HTML autocomplete |
403
412
 
404
413
  > **注意:** `URL` 型は常にマッチします。これは相対 URL がほぼあらゆる文字列を受け入れるためです。URL 形式を厳密に検証したい場合は、`AbsoluteURL` や `HTTPSchemaURL` を使用してください。
405
414
 
@@ -422,7 +431,7 @@ export function check(value: string, type: ReadonlyDeep<Type>, ref?: string, cac
422
431
 
423
432
  ```mermaid
424
433
  flowchart LR
425
- A["gen/specific-schema.json<br/>(List, Enum, Number, Directive)"] --> D["gen/types.ts<br/>(ジェネレータスクリプト)"]
434
+ A["gen/specific-schema.json<br/>(List, Enum, Number, Directive, Pattern)"] --> D["gen/types.ts<br/>(ジェネレータスクリプト)"]
426
435
  B["css-tree lexer<br/>(CSS プロパティ + 型)"] --> D
427
436
  C["src/defs.ts + src/css-defs.ts<br/>(拡張型)"] --> D
428
437
  E["src/css-tokenizers.ts<br/>(カスタムトークナイザ)"] --> D
@@ -432,7 +441,7 @@ flowchart LR
432
441
 
433
442
  ### 仕組み
434
443
 
435
- 1. **`gen/specific-schema.json`** は `List`、`Enum`、`Number`、`Directive`(キーワード以外の型バリアント)の JSON Schema を定義しています。
444
+ 1. **`gen/specific-schema.json`** は `List`、`Enum`、`Number`、`Directive`、`Pattern`(キーワード以外の型バリアント)の JSON Schema を定義しています。
436
445
 
437
446
  2. **`gen/types.ts`** はジェネレータスクリプトです。以下の処理を行います。
438
447
  - css-tree のレキサーからすべての CSS プロパティ名と型名を取得
@@ -445,10 +454,10 @@ flowchart LR
445
454
  - `extended-type` -- すべてのカスタム型識別子の文字列 enum
446
455
  - `html-attr-requirement` -- 現時点では `["Boolean"]` のみ
447
456
  - `keyword-defined-type` -- 上記 3 つの `oneOf`
448
- - `list`、`enum`、`number`、`directive` -- `specific-schema.json` からの定義
449
- - `type` -- 5 つの型バリアントすべての `oneOf`
457
+ - `list`、`enum`、`number`、`directive`、`pattern` -- `specific-schema.json` からの定義
458
+ - `type` -- 6 つの型バリアントすべての `oneOf`
450
459
 
451
- 4. **`src/types.schema.ts`** は `types.schema.json` から `json-schema-to-typescript` を使って生成されます。コードベースの他の部分がインポートする TypeScript 型(`Type`、`List`、`Enum`、`Number`、`Directive`、`KeywordDefinedType`、`CssSyntax`、`ExtendedType`、`HtmlAttrRequirement`)をエクスポートします。
460
+ 4. **`src/types.schema.ts`** は `types.schema.json` から `json-schema-to-typescript` を使って生成されます。コードベースの他の部分がインポートする TypeScript 型(`Type`、`List`、`Enum`、`Number`、`Directive`、`Pattern`、`KeywordDefinedType`、`CssSyntax`、`ExtendedType`、`HtmlAttrRequirement`)をエクスポートします。
452
461
 
453
462
  ### 目的
454
463
 
@@ -456,6 +465,64 @@ flowchart LR
456
465
  - **型安全性:** 生成された TypeScript 型により、コードベースがコンパイル時に有効な型識別子のみを参照できることが保証されます。
457
466
  - **唯一の信頼できるソース:** css-tree のレキサーデータベースと `defs.ts`/`cssDefs.ts` のカスタム定義が権威あるソースであり、スキーマと TypeScript 型は常にそこから導出されます。
458
467
 
468
+ ### 新しい Type バリアントを追加する
469
+
470
+ > **警告:** `packages/@markuplint/types/types.schema.json` と `packages/@markuplint/types/src/types.schema.ts` はどちらも**生成ファイル**です。絶対に直接編集しないでください — 次に `yarn workspace @markuplint/types run schema` が実行された瞬間に変更は消えます。すべての型バリアント追加は `gen/specific-schema.json` から始める必要があります。
471
+
472
+ `Type` に新しい判別共用体バリアントを追加する(`List`、`Enum`、`Number`、`Directive`、`Pattern` の仲間として)には、以下の手順に従ってください。
473
+
474
+ 1. **`gen/specific-schema.json` にバリアントを追加**
475
+ `definitions` 内の `list`、`enum`、`number`、`directive`、`pattern` の隣に新しい定義を配置します。他のバリアントと衝突しない判別キーを使用してください。例えば仮想的な `range` バリアントの場合:
476
+
477
+ ```jsonc
478
+ "range": {
479
+ "type": "object",
480
+ "required": ["range"],
481
+ "additionalProperties": false,
482
+ "properties": {
483
+ "range": {
484
+ "type": "object",
485
+ "required": ["from", "to"],
486
+ "properties": {
487
+ "from": { "type": "number" },
488
+ "to": { "type": "number" }
489
+ }
490
+ }
491
+ }
492
+ }
493
+ ```
494
+
495
+ キー(`range`)が判別子になります。`gen/types.ts` はトップレベルの `type.oneOf` に `Object.keys(specific.definitions)` を自動的に取り込むため、追加の配線は不要です。
496
+
497
+ 2. **ランタイムの型ガードとチェッカーを実装**
498
+ `src/check-base.ts` 内に、`isList`、`isEnum`、`isNumber`、`isDirective`、`isPattern` と並べて **`export` された** `isRange()` 関数を追加します。`check-base.ts` の型ガードはすべて `export` されています — 下流パッケージが自分で `Type` 値を narrowing するケースに対応するためです。対応する `checkRange()` 関数は、`check-base.ts` 内にインラインで実装するか、専用ファイル(例: `src/range.ts`、`src/pattern.ts` と同じパターン)に切り出します。最後に、`checkBase` ディスパッチャーに新しい分岐を配線し、既存バリアントと順序を揃えてください。
499
+
500
+ 3. **新しい TypeScript 型とランタイムチェッカーを再 export**
501
+ 再生成後、interface(例: `Range`)は `src/types.schema.ts` から利用可能になります。このパッケージの型 re-export は 2 層構造です:
502
+ - **型** — `src/types.ts`(`types.schema.ts` をプロキシする型のハブ)の再 export リストに `Pattern`、`Directive` などと並べて `Range` を追加します。`src/index.ts` はその後 `export type * from './types.js'` ですべての型を転送するので、型については `src/index.ts` に変更は不要です。
503
+ - **ランタイムチェッカー** — `checkRange()` を `src/range.ts` のような専用ファイルに配置した場合は、`src/index.ts` に明示的な named export を追加します(`export { checkPattern } from './pattern.js';` がどのように配線されているか参照してください)。
504
+
505
+ **`src/index.ts` に型を直接追加してはいけません** — `src/types.ts` のハブをバイパスし、下流パッケージの import パスが一貫しなくなります。
506
+
507
+ 4. **スキーマと型を再生成**
508
+
509
+ ```bash
510
+ yarn workspace @markuplint/types run schema
511
+ yarn lint # oxfmt でフォーマット
512
+ yarn build # 生成された型がコンパイルできるか検証
513
+ yarn test # フルスイートを実行
514
+ ```
515
+
516
+ 5. **テストを追加**
517
+ ランタイムチェッカーと統合パスの両方をカバーしてください:
518
+ - **ランタイムチェッカーテスト**: `src/check.spec.ts:29-40` の `Pattern` テストパターンを参照 — キーワード形式(`check('.*', 'Pattern')`)とオブジェクト形式(`check('hello', { pattern: '/^he/' })`)の両方がテストされています。新しいバリアントも同じ構造を模倣してください。
519
+ - **統合テスト**: バリアントが `@markuplint/ml-spec` から消費される場合は、`@markuplint/rules/src/helpers.spec.ts`(`[helpers-issue-3685-*]` テストのパターン参照)に新しいバリアントを宣言する属性仕様を `isValidAttr` が処理できることを示すテストを追加してください。
520
+
521
+ 6. **新しいバリアントをドキュメント化**
522
+ このファイルの **型の共用体(Type Union)** 表と **仕組み** セクションを更新し、バリアント数(現在 6 つ)を実装と同期させてください。
523
+
524
+ `Pattern` バリアントはこの手順で追加されました(commit `06528bd63`)。実例として参照してください。
525
+
459
526
  ## CSS 定義
460
527
 
461
528
  型システムは、css-tree の機能を拡張する 3 つのモジュールを通じて CSS と深く統合されています。
@@ -6,14 +6,14 @@ The `@markuplint/types` package provides a type system for validating HTML attri
6
6
 
7
7
  ## Type Union
8
8
 
9
- The core of the type system is a five-member union type called `Type`, defined in `src/types.schema.ts`:
9
+ The core of the type system is a six-member union type called `Type`, defined in `src/types.schema.ts`:
10
10
 
11
11
  ```ts
12
12
  // src/types.schema.ts
13
- export type Type = KeywordDefinedType | List | Enum | Number | Directive;
13
+ export type Type = KeywordDefinedType | List | Enum | Number | Directive | Pattern;
14
14
  ```
15
15
 
16
- Every attribute value specification in markuplint resolves to one of these five forms. The dispatcher in `src/check-base.ts` identifies which variant is being used and routes to the corresponding checker:
16
+ Every attribute value specification in markuplint resolves to one of these six forms. The dispatcher in `src/check-base.ts` identifies which variant is being used and routes to the corresponding checker:
17
17
 
18
18
  ```ts
19
19
  // src/check-base.ts
@@ -23,6 +23,7 @@ export function checkBase(value: string, type: ReadonlyDeep<Type>, defs: Defs, r
23
23
  if (isEnum(type)) return checkEnum(value, type, ref);
24
24
  if (isNumber(type)) return checkNumber(value, type, ref);
25
25
  if (isDirective(type)) return checkDirective(value, type, defs, ref, cache);
26
+ if (isPattern(type)) return checkPattern(value, type);
26
27
  throw new Error('Unknown type');
27
28
  }
28
29
  ```
@@ -36,6 +37,7 @@ export function checkBase(value: string, type: ReadonlyDeep<Type>, defs: Defs, r
36
37
  | **Enum** | `object` | `'enum' in type` | Fixed set of allowed string values | `{ enum: ["auto", "ltr", "rtl"] }` |
37
38
  | **Number** | `object` | `type.type === 'float' \| 'integer'` | Numeric values with optional range constraints | `{ type: "integer", gte: 0 }` |
38
39
  | **Directive** | `object` | `'directive' in type` | Composite attribute values with separators | `{ directive: [";"], token: "URL" }` |
40
+ | **Pattern** | `object` | `'pattern' in type` | Regex or exact-string value matching | `{ pattern: "/^[a-z]+$/i" }` |
39
41
 
40
42
  ### KeywordDefinedType
41
43
 
@@ -385,21 +387,28 @@ SourceSizeList: {
385
387
 
386
388
  The `defs` object in `src/defs.ts` registers over 30 built-in types. Here are some notable entries:
387
389
 
388
- | Type Identifier | Validation Method | Specification |
389
- | ------------------- | -------------------------- | ---------------------- |
390
- | `Any` | Always matches | -- |
391
- | `NoEmptyAny` | Rejects empty strings | -- |
392
- | `Number` | Floating-point check | -- |
393
- | `Int` | Integer check | -- |
394
- | `Uint` | Non-negative integer check | -- |
395
- | `URL` | Always matches (see note) | WHATWG URL |
396
- | `DOMID` | No whitespace, non-empty | HTML #id |
397
- | `DateTime` | Full datetime parsing | WHATWG datetime |
398
- | `BCP47` | RFC BCP 47 language tag | IETF BCP 47 |
399
- | `CustomElementName` | Valid custom element name | WHATWG Custom Elements |
400
- | `MIMEType` | MIME type parsing | MIME Sniffing |
401
- | `SourceSizeList` | CSS syntax-based | HTML `<img sizes>` |
402
- | `AutoComplete` | Complex multi-token | HTML autocomplete |
390
+ | Type Identifier | Validation Method | Specification |
391
+ | --------------------- | ----------------------------------- | ---------------------- |
392
+ | `Any` | Always matches | -- |
393
+ | `NoEmptyAny` | Rejects empty strings | -- |
394
+ | `Number` | Floating-point check | -- |
395
+ | `Int` | Integer check | -- |
396
+ | `Uint` | Non-negative integer check | -- |
397
+ | `URL` | Always matches (see note) | WHATWG URL |
398
+ | `DOMID` | No whitespace, non-empty | HTML #id |
399
+ | `DateTime` | Full datetime parsing | WHATWG datetime |
400
+ | `DateString` | Date only (`YYYY-MM-DD`) | WHATWG dates |
401
+ | `TimeString` | Time only (`hh:mm[:ss]`) | WHATWG times |
402
+ | `MonthString` | Month only (`YYYY-MM`) | WHATWG months |
403
+ | `WeekString` | Week only (`YYYY-Www`) | WHATWG weeks |
404
+ | `LocalDateTimeString` | Local datetime (`YYYY-MM-DDThh:mm`) | WHATWG local dates |
405
+ | `SimpleColor` | Simple color (`#rrggbb`) | WHATWG simple colour |
406
+ | `Email` | Email address (ASCII only) | HTML valid email |
407
+ | `BCP47` | RFC BCP 47 language tag | IETF BCP 47 |
408
+ | `CustomElementName` | Valid custom element name | WHATWG Custom Elements |
409
+ | `MIMEType` | MIME type parsing | MIME Sniffing |
410
+ | `SourceSizeList` | CSS syntax-based | HTML `<img sizes>` |
411
+ | `AutoComplete` | Complex multi-token | HTML autocomplete |
403
412
 
404
413
  > **Note:** The `URL` type always matches because relative URLs accept almost any string. To validate URL format strictly, use `AbsoluteURL` or `HTTPSchemaURL` instead.
405
414
 
@@ -422,7 +431,7 @@ The type system's type definitions are available both as TypeScript types and as
422
431
 
423
432
  ```mermaid
424
433
  flowchart LR
425
- A["gen/specific-schema.json<br/>(List, Enum, Number, Directive)"] --> D["gen/types.ts<br/>(generator script)"]
434
+ A["gen/specific-schema.json<br/>(List, Enum, Number, Directive, Pattern)"] --> D["gen/types.ts<br/>(generator script)"]
426
435
  B["css-tree lexer<br/>(CSS properties + types)"] --> D
427
436
  C["src/defs.ts + src/css-defs.ts<br/>(extended types)"] --> D
428
437
  E["src/css-tokenizers.ts<br/>(custom tokenizers)"] --> D
@@ -432,7 +441,7 @@ flowchart LR
432
441
 
433
442
  ### How It Works
434
443
 
435
- 1. **`gen/specific-schema.json`** defines the JSON Schema for `List`, `Enum`, `Number`, and `Directive` -- the non-keyword type variants.
444
+ 1. **`gen/specific-schema.json`** defines the JSON Schema for `List`, `Enum`, `Number`, `Directive`, and `Pattern` -- the non-keyword type variants.
436
445
 
437
446
  2. **`gen/types.ts`** is the generator script. It:
438
447
  - Reads all CSS property names and type names from css-tree's lexer
@@ -445,10 +454,10 @@ flowchart LR
445
454
  - `extended-type` -- All custom type identifiers as a string enum
446
455
  - `html-attr-requirement` -- Currently just `["Boolean"]`
447
456
  - `keyword-defined-type` -- A `oneOf` of the three above
448
- - `list`, `enum`, `number`, `directive` -- From `specific-schema.json`
449
- - `type` -- A `oneOf` of all five type variants
457
+ - `list`, `enum`, `number`, `directive`, `pattern` -- From `specific-schema.json`
458
+ - `type` -- A `oneOf` of all six type variants
450
459
 
451
- 4. **`src/types.schema.ts`** is generated from `types.schema.json` using `json-schema-to-typescript`. This file exports the TypeScript types (`Type`, `List`, `Enum`, `Number`, `Directive`, `KeywordDefinedType`, `CssSyntax`, `ExtendedType`, `HtmlAttrRequirement`) that the rest of the codebase imports.
460
+ 4. **`src/types.schema.ts`** is generated from `types.schema.json` using `json-schema-to-typescript`. This file exports the TypeScript types (`Type`, `List`, `Enum`, `Number`, `Directive`, `Pattern`, `KeywordDefinedType`, `CssSyntax`, `ExtendedType`, `HtmlAttrRequirement`) that the rest of the codebase imports.
452
461
 
453
462
  ### Purpose
454
463
 
@@ -456,6 +465,64 @@ flowchart LR
456
465
  - **Type safety:** The generated TypeScript types ensure that the codebase can only reference valid type identifiers at compile time.
457
466
  - **Single source of truth:** CSS-tree's lexer database and the custom definitions in `defs.ts`/`cssDefs.ts` are the authoritative sources; the schema and TypeScript types are always derived from them.
458
467
 
468
+ ### Adding a New Type Variant
469
+
470
+ > **Warning:** `packages/@markuplint/types/types.schema.json` and `packages/@markuplint/types/src/types.schema.ts` are both **generated files**. Never hand-edit them — your changes will be blown away the next time `yarn workspace @markuplint/types run schema` runs. All type-variant additions must start from `gen/specific-schema.json`.
471
+
472
+ To add a brand-new discriminated variant of `Type` (alongside `List`, `Enum`, `Number`, `Directive`, `Pattern`), follow this recipe:
473
+
474
+ 1. **Add the variant to `gen/specific-schema.json`**
475
+ Place the new definition alongside `list`, `enum`, `number`, `directive`, `pattern` inside `definitions`. Use a discriminant key that does not collide with the other variants. For example, a hypothetical `range` variant:
476
+
477
+ ```jsonc
478
+ "range": {
479
+ "type": "object",
480
+ "required": ["range"],
481
+ "additionalProperties": false,
482
+ "properties": {
483
+ "range": {
484
+ "type": "object",
485
+ "required": ["from", "to"],
486
+ "properties": {
487
+ "from": { "type": "number" },
488
+ "to": { "type": "number" }
489
+ }
490
+ }
491
+ }
492
+ }
493
+ ```
494
+
495
+ The key (`range`) becomes the discriminant; `gen/types.ts` auto-includes every `Object.keys(specific.definitions)` entry into the top-level `type.oneOf`, so no additional wiring is needed.
496
+
497
+ 2. **Implement the runtime type guard and checker**
498
+ Add an **exported** `isRange()` function to `src/check-base.ts` next to `isList`, `isEnum`, `isNumber`, `isDirective`, `isPattern`. The type guards in `check-base.ts` are all `export`ed because downstream packages may narrow a `Type` value themselves. Implement the matching `checkRange()` function either inline in `check-base.ts` or in a dedicated file (e.g. `src/range.ts`, mirroring `src/pattern.ts`). Finally, wire the new branch into the `checkBase` dispatcher, keeping the order consistent with the existing variants.
499
+
500
+ 3. **Re-export the new TypeScript type and runtime checker**
501
+ After regeneration, the interface (e.g. `Range`) is available from `src/types.schema.ts`. The type re-export flow in this package is two-layered:
502
+ - **Types** — Add `Range` to the re-export list in `src/types.ts` (the central type hub that proxies `types.schema.ts`), alongside `Pattern`, `Directive`, etc. `src/index.ts` then forwards all types via its `export type * from './types.js'` line, so no change is needed there for the type.
503
+ - **Runtime checker** — If you placed `checkRange()` in a dedicated file like `src/range.ts`, add an explicit named export in `src/index.ts` (see how `export { checkPattern } from './pattern.js';` is wired).
504
+
505
+ Do **not** add types directly to `src/index.ts`; that bypasses the `src/types.ts` hub and makes downstream import paths inconsistent.
506
+
507
+ 4. **Regenerate the schema and types**
508
+
509
+ ```bash
510
+ yarn workspace @markuplint/types run schema
511
+ yarn lint # formats with oxfmt
512
+ yarn build # verifies the generated types compile
513
+ yarn test # runs the full suite
514
+ ```
515
+
516
+ 5. **Add tests**
517
+ Cover both the runtime checker and the integration path:
518
+ - **Runtime checker tests**: See `src/check.spec.ts:29-40` for the `Pattern` variant's test pattern — both the keyword form (`check('.*', 'Pattern')`) and the object form (`check('hello', { pattern: '/^he/' })`) are tested. Mirror the same structure for your new variant.
519
+ - **Integration tests**: If the variant is consumed by `@markuplint/ml-spec`, add a test in `@markuplint/rules/src/helpers.spec.ts` showing that `isValidAttr` handles an attribute spec declaring your new variant.
520
+
521
+ 6. **Document the new variant**
522
+ Update this file's **Type Union** table and the **How It Works** section so the count of variants (currently six) stays in sync with the implementation.
523
+
524
+ The `Pattern` variant was added this way (commit `06528bd63`); use it as a working reference.
525
+
459
526
  ## CSS Definitions
460
527
 
461
528
  The type system integrates deeply with CSS through three modules that extend css-tree's capabilities.
@@ -84,6 +84,18 @@ export function isFloat(value: string) {
84
84
 
85
85
  WHATWGバリデータは、[HTML Living Standard](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html)で定義されたマイクロシンタックスを実装しています。
86
86
 
87
+ ### SimpleColor
88
+
89
+ **ファイル:** `src/whatwg/check-simple-color.ts`
90
+
91
+ [valid simple color](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-simple-colour) を検証します。正確に7文字 — `#` に続く6桁のASCII16進数字(例: `#ff0000`)です。CSSの `<color>` 型(名前付き色、`rgb()`、`hsl()` 等を受け入れる)とは**異なります**。`input[type=color]` の value 属性バリデーションに使用されます。
92
+
93
+ ### Email
94
+
95
+ **ファイル:** `src/whatwg/check-email.ts`
96
+
97
+ HTML仕様に基づく [valid email address](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address) を検証します。正規表現は仕様からの逐語的なパターンで、ASCII専用マッチングを保証するため `\w` ではなく明示的なASCII範囲(`[a-zA-Z0-9]`)を使用しています。`input[type=email]` の value 属性バリデーションに使用されます。
98
+
87
99
  ### DateTimeサブシステム
88
100
 
89
101
  DateTimeサブシステムは、WHATWG仕様が定義するすべての日時フォーマットを検証します。12個の個別フォーマットチェッカーで構成されており、共通のトークン検証レイヤーを共有する最も複雑なバリデータ群です。
@@ -84,6 +84,18 @@ export function isFloat(value: string) {
84
84
 
85
85
  WHATWG validators implement the microsyntaxes defined in the [HTML Living Standard](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html).
86
86
 
87
+ ### SimpleColor
88
+
89
+ **File:** `src/whatwg/check-simple-color.ts`
90
+
91
+ Validates a [valid simple color](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-simple-colour): exactly seven characters — `#` followed by six ASCII hex digits (e.g., `#ff0000`). This is **not** the same as the CSS `<color>` type, which accepts named colors, `rgb()`, `hsl()`, etc. Used for `input[type=color]` value validation.
92
+
93
+ ### Email
94
+
95
+ **File:** `src/whatwg/check-email.ts`
96
+
97
+ Validates a [valid email address](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address) per the HTML spec. The regex is the verbatim pattern from the spec, using explicit ASCII ranges (`[a-zA-Z0-9]`) instead of `\w` to guarantee ASCII-only matching. Used for `input[type=email]` value validation.
98
+
87
99
  ### DateTime Subsystem
88
100
 
89
101
  The DateTime subsystem validates all date and time formats defined by the WHATWG specification. It is the most complex validator group, consisting of 12 individual format checkers that share a common token validation layer.
@@ -10,4 +10,4 @@ import type { CustomSyntaxCheck, UnmatchedResult } from './types.js';
10
10
  * @param checks - The array of type check functions to try
11
11
  * @returns The matched result or the best unmatched result
12
12
  */
13
- export declare function checkMultiTypes(value: string, checks: readonly CustomSyntaxCheck[]): UnmatchedResult | import("./types.js").MatchedResult;
13
+ export declare function checkMultiTypes(value: string, checks: readonly CustomSyntaxCheck[]): import("./types.js").MatchedResult | UnmatchedResult;