@css-modules-kit/core 0.0.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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/dist/checker.d.ts +7 -0
  3. package/dist/checker.d.ts.map +1 -0
  4. package/dist/checker.js +46 -0
  5. package/dist/checker.js.map +1 -0
  6. package/dist/config.d.ts +101 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +200 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/diagnostic.d.ts +34 -0
  11. package/dist/diagnostic.d.ts.map +1 -0
  12. package/dist/diagnostic.js +3 -0
  13. package/dist/diagnostic.js.map +1 -0
  14. package/dist/dts-creator.d.ts +56 -0
  15. package/dist/dts-creator.d.ts.map +1 -0
  16. package/dist/dts-creator.js +95 -0
  17. package/dist/dts-creator.js.map +1 -0
  18. package/dist/error.d.ts +8 -0
  19. package/dist/error.d.ts.map +1 -0
  20. package/dist/error.js +18 -0
  21. package/dist/error.js.map +1 -0
  22. package/dist/export-builder.d.ts +23 -0
  23. package/dist/export-builder.d.ts.map +1 -0
  24. package/dist/export-builder.js +34 -0
  25. package/dist/export-builder.js.map +1 -0
  26. package/dist/external-file.d.ts +2 -0
  27. package/dist/external-file.d.ts.map +1 -0
  28. package/dist/external-file.js +3 -0
  29. package/dist/external-file.js.map +1 -0
  30. package/dist/file.d.ts +28 -0
  31. package/dist/file.d.ts.map +1 -0
  32. package/dist/file.js +73 -0
  33. package/dist/file.js.map +1 -0
  34. package/dist/index.d.ts +14 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +39 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/language-plugin.d.ts +23 -0
  39. package/dist/language-plugin.d.ts.map +1 -0
  40. package/dist/language-plugin.js +73 -0
  41. package/dist/language-plugin.js.map +1 -0
  42. package/dist/parser/at-import-parser.d.ts +14 -0
  43. package/dist/parser/at-import-parser.d.ts.map +1 -0
  44. package/dist/parser/at-import-parser.js +47 -0
  45. package/dist/parser/at-import-parser.js.map +1 -0
  46. package/dist/parser/at-value-parser.d.ts +44 -0
  47. package/dist/parser/at-value-parser.d.ts.map +1 -0
  48. package/dist/parser/at-value-parser.js +147 -0
  49. package/dist/parser/at-value-parser.js.map +1 -0
  50. package/dist/parser/css-module-parser.d.ts +93 -0
  51. package/dist/parser/css-module-parser.d.ts.map +1 -0
  52. package/dist/parser/css-module-parser.js +96 -0
  53. package/dist/parser/css-module-parser.js.map +1 -0
  54. package/dist/parser/diagnostic.d.ts +34 -0
  55. package/dist/parser/diagnostic.d.ts.map +1 -0
  56. package/dist/parser/diagnostic.js +3 -0
  57. package/dist/parser/diagnostic.js.map +1 -0
  58. package/dist/parser/location.d.ts +34 -0
  59. package/dist/parser/location.d.ts.map +1 -0
  60. package/dist/parser/location.js +9 -0
  61. package/dist/parser/location.js.map +1 -0
  62. package/dist/parser/rule-parser.d.ts +19 -0
  63. package/dist/parser/rule-parser.d.ts.map +1 -0
  64. package/dist/parser/rule-parser.js +122 -0
  65. package/dist/parser/rule-parser.js.map +1 -0
  66. package/dist/path.d.ts +11 -0
  67. package/dist/path.d.ts.map +1 -0
  68. package/dist/path.js +43 -0
  69. package/dist/path.js.map +1 -0
  70. package/dist/resolver.d.ts +13 -0
  71. package/dist/resolver.d.ts.map +1 -0
  72. package/dist/resolver.js +50 -0
  73. package/dist/resolver.js.map +1 -0
  74. package/dist/semantic-builder.d.ts +23 -0
  75. package/dist/semantic-builder.d.ts.map +1 -0
  76. package/dist/semantic-builder.js +33 -0
  77. package/dist/semantic-builder.js.map +1 -0
  78. package/dist/util.d.ts +2 -0
  79. package/dist/util.d.ts.map +1 -0
  80. package/dist/util.js +7 -0
  81. package/dist/util.js.map +1 -0
  82. package/package.json +54 -0
  83. package/src/checker.ts +66 -0
  84. package/src/config.ts +285 -0
  85. package/src/diagnostic.ts +37 -0
  86. package/src/dts-creator.ts +128 -0
  87. package/src/error.ts +13 -0
  88. package/src/export-builder.ts +51 -0
  89. package/src/file.ts +83 -0
  90. package/src/index.ts +38 -0
  91. package/src/parser/at-import-parser.ts +47 -0
  92. package/src/parser/at-value-parser.ts +177 -0
  93. package/src/parser/css-module-parser.ts +193 -0
  94. package/src/parser/location.ts +43 -0
  95. package/src/parser/rule-parser.ts +140 -0
  96. package/src/path.ts +40 -0
  97. package/src/resolver.ts +57 -0
  98. package/src/typing/typescript.d.ts +19 -0
  99. package/src/util.ts +3 -0
@@ -0,0 +1,193 @@
1
+ import type { AtRule, Node, Root, Rule } from 'postcss';
2
+ import { CssSyntaxError, parse } from 'postcss';
3
+ import safeParser from 'postcss-safe-parser';
4
+ import type { SyntacticDiagnostic } from '../diagnostic.js';
5
+ import { parseAtImport } from './at-import-parser.js';
6
+ import { parseAtValue } from './at-value-parser.js';
7
+ import { type Location } from './location.js';
8
+ import { parseRule } from './rule-parser.js';
9
+
10
+ type AtImport = AtRule & { name: 'import' };
11
+ type AtValue = AtRule & { name: 'value' };
12
+
13
+ function isAtRuleNode(node: Node): node is AtRule {
14
+ return node.type === 'atrule';
15
+ }
16
+
17
+ function isAtImportNode(node: Node): node is AtImport {
18
+ return isAtRuleNode(node) && node.name === 'import';
19
+ }
20
+
21
+ function isAtValueNode(node: Node): node is AtValue {
22
+ return isAtRuleNode(node) && node.name === 'value';
23
+ }
24
+
25
+ function isRuleNode(node: Node): node is Rule {
26
+ return node.type === 'rule';
27
+ }
28
+
29
+ /**
30
+ * Collect tokens from the AST.
31
+ */
32
+ function collectTokens(ast: Root) {
33
+ const allDiagnostics: SyntacticDiagnostic[] = [];
34
+ const localTokens: Token[] = [];
35
+ const tokenImporters: TokenImporter[] = [];
36
+ ast.walk((node) => {
37
+ if (isAtImportNode(node)) {
38
+ const parsed = parseAtImport(node);
39
+ if (parsed !== undefined) {
40
+ tokenImporters.push({ type: 'import', ...parsed });
41
+ }
42
+ } else if (isAtValueNode(node)) {
43
+ const { atValue, diagnostics } = parseAtValue(node);
44
+ allDiagnostics.push(...diagnostics);
45
+ if (atValue === undefined) return;
46
+ if (atValue.type === 'valueDeclaration') {
47
+ localTokens.push({ name: atValue.name, loc: atValue.loc });
48
+ } else if (atValue.type === 'valueImportDeclaration') {
49
+ tokenImporters.push({ ...atValue, type: 'value' });
50
+ }
51
+ } else if (isRuleNode(node)) {
52
+ const { classSelectors, diagnostics } = parseRule(node);
53
+ allDiagnostics.push(...diagnostics);
54
+ for (const classSelector of classSelectors) {
55
+ localTokens.push(classSelector);
56
+ }
57
+ }
58
+ });
59
+ return { localTokens, tokenImporters, diagnostics: allDiagnostics };
60
+ }
61
+
62
+ /** The item being exported from a CSS module file (a.k.a. token). */
63
+ export interface Token {
64
+ /** The token name. */
65
+ name: string;
66
+ /** The location of the token in the source file. */
67
+ loc: Location;
68
+ }
69
+
70
+ /**
71
+ * A token importer using `@import '...'`.
72
+ * `@import` imports all tokens from the file. Therefore, it does not have
73
+ * the name of the imported token unlike {@link AtValueTokenImporter}.
74
+ */
75
+ export interface AtImportTokenImporter {
76
+ type: 'import';
77
+ /**
78
+ * The specifier of the file from which the token is imported.
79
+ * This is a string before being resolved and unquoted.
80
+ * @example `@import './a.module.css'` would have `from` as `'./a.module.css'`.
81
+ */
82
+ from: string;
83
+ /** The location of the `from` in *.module.css file. */
84
+ fromLoc: Location;
85
+ }
86
+
87
+ /** A token importer using `@value ... from '...'`. */
88
+ export interface AtValueTokenImporter {
89
+ type: 'value';
90
+ /** The values imported from the file. */
91
+ values: AtValueTokenImporterValue[];
92
+ /**
93
+ * The specifier of the file from which the token is imported.
94
+ * This is a string before being resolved and unquoted.
95
+ * @example `@value a from './a.module.css'` would have `from` as `'./a.module.css'`.
96
+ */
97
+ from: string;
98
+ /** The location of the `from` in *.module.css file. */
99
+ fromLoc: Location;
100
+ }
101
+
102
+ /** A value imported from a CSS module file using `@value ... from '...'`. */
103
+ export interface AtValueTokenImporterValue {
104
+ /**
105
+ * The name of the token in the file from which it is imported.
106
+ * @example `@value a from './a.module.css'` would have `name` as `'a'`.
107
+ * @example `@value a as b from './a.module.css'` would have `name` as `'a'`.
108
+ */
109
+ name: string;
110
+ /** The location of the `name` in *.module.css file. */
111
+ loc: Location;
112
+ /**
113
+ * The name of the token in the current file.
114
+ * @example `@value a from './a.module.css'` would not have `localName`.
115
+ * @example `@value a as b from './a.module.css'` would have `localName` as `'b'`.
116
+ */
117
+ localName?: string;
118
+ /**
119
+ * The location of the `localName` in *.module.css file.
120
+ * This is `undefined` when `localName` is `undefined`.
121
+ */
122
+ localLoc?: Location;
123
+ }
124
+
125
+ export type TokenImporter = AtImportTokenImporter | AtValueTokenImporter;
126
+
127
+ export interface CSSModule {
128
+ /** Absolute path of the file */
129
+ fileName: string;
130
+ /**
131
+ * List of token names defined in the file.
132
+ * @example
133
+ * Consider the following file:
134
+ * ```css
135
+ * .foo { color: red }
136
+ * .bar, .baz { color: red }
137
+ * ```
138
+ * The `localTokens` for this file would be `['foo', 'bar', 'baz']`.
139
+ */
140
+ localTokens: Token[];
141
+ /**
142
+ * List of token importers in the file.
143
+ * Token importer is a statement that imports tokens from another file.
144
+ */
145
+ tokenImporters: TokenImporter[];
146
+ }
147
+
148
+ export interface ParseCSSModuleOptions {
149
+ fileName: string;
150
+ dashedIdents: boolean;
151
+ safe: boolean;
152
+ }
153
+
154
+ export interface ParseCSSModuleResult {
155
+ cssModule: CSSModule;
156
+ diagnostics: SyntacticDiagnostic[];
157
+ }
158
+
159
+ export function parseCSSModule(text: string, { fileName, safe }: ParseCSSModuleOptions): ParseCSSModuleResult {
160
+ let ast: Root;
161
+ try {
162
+ const parser = safe ? safeParser : parse;
163
+ ast = parser(text, { from: fileName });
164
+ } catch (e) {
165
+ if (e instanceof CssSyntaxError) {
166
+ const start = { line: e.line ?? 1, column: e.column ?? 1 };
167
+ return {
168
+ cssModule: { fileName, localTokens: [], tokenImporters: [] },
169
+ diagnostics: [
170
+ {
171
+ type: 'syntactic',
172
+ fileName,
173
+ start,
174
+ ...(e.endLine !== undefined &&
175
+ e.endColumn !== undefined && {
176
+ end: { line: e.endLine, column: e.endColumn },
177
+ }),
178
+ text: e.reason,
179
+ category: 'error',
180
+ },
181
+ ],
182
+ };
183
+ }
184
+ throw e;
185
+ }
186
+ const { localTokens, tokenImporters, diagnostics } = collectTokens(ast);
187
+ const cssModule = {
188
+ fileName,
189
+ localTokens,
190
+ tokenImporters,
191
+ };
192
+ return { cssModule, diagnostics };
193
+ }
@@ -0,0 +1,43 @@
1
+ import type { Rule } from 'postcss';
2
+ import type selectorParser from 'postcss-selector-parser';
3
+ import type { DiagnosticPosition } from '../diagnostic.js';
4
+
5
+ export interface Position {
6
+ /**
7
+ * The line number in the source file. It is 1-based.
8
+ * This is compatible with postcss and tsserver.
9
+ */
10
+ // TODO: Maybe it should be deleted since it is not used
11
+ line: number;
12
+ /**
13
+ * The column number in the source file. It is 1-based.
14
+ * This is compatible with postcss and tsserver.
15
+ */
16
+ // TODO: Maybe it should be deleted since it is not used
17
+ column: number;
18
+ /** The offset in the source file. It is 0-based. */
19
+ offset: number;
20
+ }
21
+
22
+ export interface Location {
23
+ /**
24
+ * The starting position of the node. It is inclusive.
25
+ * This is compatible with postcss and tsserver.
26
+ */
27
+ start: Position;
28
+ /**
29
+ * The ending position of the node. It is exclusive.
30
+ * This is compatible with tsserver, but not postcss.
31
+ */
32
+ // TODO: Maybe it should be deleted since it is not used
33
+ end: Position;
34
+ }
35
+
36
+ export function calcDiagnosticsLocationForSelectorParserNode(
37
+ rule: Rule,
38
+ node: selectorParser.Node,
39
+ ): { start: DiagnosticPosition; end: DiagnosticPosition } {
40
+ const start = rule.positionBy({ index: node.sourceIndex });
41
+ const end = rule.positionBy({ index: node.sourceIndex + node.toString().length });
42
+ return { start, end };
43
+ }
@@ -0,0 +1,140 @@
1
+ import type { Rule } from 'postcss';
2
+ import selectorParser from 'postcss-selector-parser';
3
+ import type { SyntacticDiagnostic } from '../diagnostic.js';
4
+ import { calcDiagnosticsLocationForSelectorParserNode, type Location } from './location.js';
5
+
6
+ interface CollectResult {
7
+ classNames: selectorParser.ClassName[];
8
+ diagnostics: SyntacticDiagnostic[];
9
+ }
10
+
11
+ function flatCollectResults(results: CollectResult[]): CollectResult {
12
+ const classNames: selectorParser.ClassName[] = [];
13
+ const diagnostics: SyntacticDiagnostic[] = [];
14
+ for (const result of results) {
15
+ classNames.push(...result.classNames);
16
+ diagnostics.push(...result.diagnostics);
17
+ }
18
+ return { classNames, diagnostics };
19
+ }
20
+
21
+ const JS_IDENTIFIER_PATTERN = /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u;
22
+
23
+ function convertClassNameToCollectResult(rule: Rule, node: selectorParser.ClassName): CollectResult {
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- `raws` property is defined if `node` has escaped characters.
25
+ const name = (node as any).raws?.value ?? node.value;
26
+
27
+ if (!JS_IDENTIFIER_PATTERN.test(name)) {
28
+ const diagnostic: SyntacticDiagnostic = {
29
+ type: 'syntactic',
30
+ fileName: rule.source!.input.file!,
31
+ ...calcDiagnosticsLocationForSelectorParserNode(rule, node),
32
+ text: `\`${name}\` is not allowed because it is not a valid JavaScript identifier.`,
33
+ category: 'error',
34
+ };
35
+ return { classNames: [], diagnostics: [diagnostic] };
36
+ }
37
+ return { classNames: [node], diagnostics: [] };
38
+ }
39
+
40
+ /**
41
+ * Collect local class names from the AST.
42
+ * This function is based on the behavior of postcss-modules-local-by-default.
43
+ *
44
+ * @see https://github.com/css-modules/postcss-modules-local-by-default/blob/38119276608ef14821797cfc0242b3c7dead69af/src/index.js
45
+ * @see https://github.com/css-modules/postcss-modules-local-by-default/blob/38119276608ef14821797cfc0242b3c7dead69af/test/index.test.js
46
+ * @example `.local1 :global(.global1) .local2 :local(.local3)` => `[".local1", ".local2", ".local3"]`
47
+ */
48
+ function collectLocalClassNames(rule: Rule, root: selectorParser.Root): CollectResult {
49
+ return visitNode(root, undefined);
50
+
51
+ function visitNode(node: selectorParser.Node, wrappedBy: ':local(...)' | ':global(...)' | undefined): CollectResult {
52
+ if (selectorParser.isClassName(node)) {
53
+ switch (wrappedBy) {
54
+ // If the class name is wrapped by `:local(...)` or `:global(...)`,
55
+ // the scope is determined by the wrapper.
56
+ case ':local(...)':
57
+ return convertClassNameToCollectResult(rule, node);
58
+ case ':global(...)':
59
+ return { classNames: [], diagnostics: [] };
60
+ // If the class name is not wrapped by `:local(...)` or `:global(...)`,
61
+ // the scope is determined by the mode.
62
+ default:
63
+ // Mode is customizable in css-loader, but we don't support it for simplicity. We fix the mode to 'local'.
64
+ return convertClassNameToCollectResult(rule, node);
65
+ }
66
+ } else if (selectorParser.isPseudo(node) && (node.value === ':local' || node.value === ':global')) {
67
+ if (node.nodes.length === 0) {
68
+ // `node` is `:local` or `:global` (without any arguments)
69
+ // We don't support `:local` and `:global` (without any arguments) because they are complex.
70
+ const diagnostic: SyntacticDiagnostic = {
71
+ type: 'syntactic',
72
+ fileName: rule.source!.input.file!,
73
+ ...calcDiagnosticsLocationForSelectorParserNode(rule, node),
74
+ text: `\`${node.value}\` is not supported. Use \`${node.value}(...)\` instead.`,
75
+ category: 'error',
76
+ };
77
+ return { classNames: [], diagnostics: [diagnostic] };
78
+ } else {
79
+ // `node` is `:local(...)` or `:global(...)` (with arguments)
80
+ if (wrappedBy !== undefined) {
81
+ const diagnostic: SyntacticDiagnostic = {
82
+ type: 'syntactic',
83
+ fileName: rule.source!.input.file!,
84
+ ...calcDiagnosticsLocationForSelectorParserNode(rule, node),
85
+ text: `A \`${node.value}(...)\` is not allowed inside of \`${wrappedBy}\`.`,
86
+ category: 'error',
87
+ };
88
+ return { classNames: [], diagnostics: [diagnostic] };
89
+ }
90
+ return flatCollectResults(
91
+ node.nodes.map((child) => visitNode(child, node.value === ':local' ? ':local(...)' : ':global(...)')),
92
+ );
93
+ }
94
+ } else if (selectorParser.isContainer(node)) {
95
+ return flatCollectResults(node.nodes.map((child) => visitNode(child, wrappedBy)));
96
+ }
97
+ return { classNames: [], diagnostics: [] };
98
+ }
99
+ }
100
+
101
+ interface ClassSelector {
102
+ /** The class name. It does not include the leading dot. */
103
+ name: string;
104
+ /** The location of the class selector. */
105
+ loc: Location;
106
+ }
107
+
108
+ interface ParseRuleResult {
109
+ classSelectors: ClassSelector[];
110
+ diagnostics: SyntacticDiagnostic[];
111
+ }
112
+
113
+ /**
114
+ * Parse a rule and collect local class selectors.
115
+ */
116
+ export function parseRule(rule: Rule): ParseRuleResult {
117
+ const root = selectorParser().astSync(rule);
118
+ const result = collectLocalClassNames(rule, root);
119
+ const classSelectors = result.classNames.map((className) => {
120
+ // If `rule` is `.a, .b { color: red; }` and `className` is `.b`,
121
+ // `rule.source` is `{ start: { line: 1, column: 1 }, end: { line: 1, column: 22 } }`
122
+ // And `className.source` is `{ start: { line: 1, column: 5 }, end: { line: 1, column: 6 } }`.
123
+ const start = {
124
+ line: rule.source!.start!.line + className.source!.start!.line - 1,
125
+ column: rule.source!.start!.column + className.source!.start!.column,
126
+ offset: rule.source!.start!.offset + className.sourceIndex + 1,
127
+ };
128
+ const end = {
129
+ // The end line is always the same as the start line, as a class selector cannot break in the middle.
130
+ line: start.line,
131
+ column: start.column + className.value.length,
132
+ offset: start.offset + className.value.length,
133
+ };
134
+ return {
135
+ name: className.value,
136
+ loc: { start, end },
137
+ };
138
+ });
139
+ return { classSelectors, diagnostics: result.diagnostics };
140
+ }
package/src/path.ts ADDED
@@ -0,0 +1,40 @@
1
+ // eslint-disable-next-line no-restricted-imports
2
+ import type { ParsedPath } from 'node:path';
3
+ // eslint-disable-next-line no-restricted-imports
4
+ import nodePath from 'node:path';
5
+ import ts from 'typescript';
6
+
7
+ export function slash(path: string): string {
8
+ return ts.server.toNormalizedPath(path);
9
+ }
10
+
11
+ export function join(...paths: string[]): string {
12
+ return slash(nodePath.join(...paths));
13
+ }
14
+
15
+ export function resolve(...paths: string[]): string {
16
+ return slash(nodePath.resolve(...paths));
17
+ }
18
+
19
+ export function relative(from: string, to: string): string {
20
+ return slash(nodePath.relative(from, to));
21
+ }
22
+
23
+ export function dirname(path: string): string {
24
+ return slash(nodePath.dirname(path));
25
+ }
26
+
27
+ export function basename(path: string): string {
28
+ return slash(nodePath.basename(path));
29
+ }
30
+
31
+ export function parse(path: string): ParsedPath {
32
+ const { root, dir, base, name, ext } = nodePath.parse(path);
33
+ return { root: slash(root), dir: slash(dir), base, name, ext };
34
+ }
35
+
36
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins, @typescript-eslint/unbound-method
37
+ export const matchesGlob = nodePath.matchesGlob;
38
+
39
+ // eslint-disable-next-line @typescript-eslint/unbound-method
40
+ export const isAbsolute = nodePath.isAbsolute;
@@ -0,0 +1,57 @@
1
+ import { fileURLToPath, pathToFileURL } from 'node:url';
2
+ import ts from 'typescript';
3
+ import { isAbsolute, resolve } from './path.js';
4
+
5
+ export interface ResolverOptions {
6
+ /** The file that imports the specifier. It is a absolute path. */
7
+ request: string;
8
+ }
9
+
10
+ /**
11
+ * A resolver function that resolves import specifiers.
12
+ * @param specifier The import specifier.
13
+ * @param options The options.
14
+ * @returns The resolved import specifier. It is a absolute path. If the import specifier cannot be resolved, return `undefined`.
15
+ */
16
+ export type Resolver = (specifier: string, options: ResolverOptions) => string | undefined;
17
+
18
+ export function createResolver(paths: Record<string, string[]>): Resolver {
19
+ return (_specifier: string, options: ResolverOptions) => {
20
+ let specifier = _specifier;
21
+
22
+ const host: ts.ModuleResolutionHost = {
23
+ ...ts.sys,
24
+ fileExists: (fileName) => {
25
+ if (fileName.endsWith('.module.d.css.ts')) {
26
+ return ts.sys.fileExists(fileName.replace(/\.module\.d\.css\.ts$/u, '.module.css'));
27
+ }
28
+ return ts.sys.fileExists(fileName);
29
+ },
30
+ };
31
+ const { resolvedModule } = ts.resolveModuleName(specifier, options.request, { paths }, host);
32
+ if (resolvedModule) {
33
+ // TODO: Logging that the paths is used.
34
+ specifier = resolvedModule.resolvedFileName.replace(/\.module\.d\.css\.ts$/u, '.module.css');
35
+ }
36
+ if (isAbsolute(specifier)) {
37
+ return resolve(specifier);
38
+ } else if (isRelativeSpecifier(specifier)) {
39
+ // Convert the specifier to an absolute path
40
+ // NOTE: Node.js resolves relative specifier with standard relative URL resolution semantics. So we will follow that here as well.
41
+ // ref: https://nodejs.org/docs/latest-v23.x/api/esm.html#terminology
42
+ return resolve(fileURLToPath(new URL(specifier, pathToFileURL(options.request)).href));
43
+ } else {
44
+ // Do not support URL or bare specifiers
45
+ // TODO: Logging that the specifier could not resolve.
46
+ return undefined;
47
+ }
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Check if the specifier is a relative specifier.
53
+ * @see https://nodejs.org/docs/latest-v23.x/api/esm.html#terminology
54
+ */
55
+ function isRelativeSpecifier(specifier: string): boolean {
56
+ return specifier.startsWith('./') || specifier.startsWith('../');
57
+ }
@@ -0,0 +1,19 @@
1
+ export {};
2
+
3
+ declare module 'typescript' {
4
+ interface FileSystemEntries {
5
+ readonly files: readonly string[];
6
+ readonly directories: readonly string[];
7
+ }
8
+ export function matchFiles(
9
+ path: string,
10
+ extensions: readonly string[] | undefined,
11
+ excludes: readonly string[] | undefined,
12
+ includes: readonly string[] | undefined,
13
+ useCaseSensitiveFileNames: boolean,
14
+ currentDirectory: string,
15
+ depth: number | undefined,
16
+ getFileSystemEntries: (path: string) => FileSystemEntries,
17
+ realpath: (path: string) => string,
18
+ ): string[];
19
+ }
package/src/util.ts ADDED
@@ -0,0 +1,3 @@
1
+ export function isPosixRelativePath(path: string): boolean {
2
+ return path.startsWith(`./`) || path.startsWith(`../`);
3
+ }