@css-modules-kit/core 0.0.5 → 0.2.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.
Files changed (72) hide show
  1. package/dist/checker.d.ts +2 -6
  2. package/dist/checker.d.ts.map +1 -1
  3. package/dist/checker.js +4 -6
  4. package/dist/checker.js.map +1 -1
  5. package/dist/config.d.ts +5 -5
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +6 -8
  8. package/dist/config.js.map +1 -1
  9. package/dist/diagnostic.d.ts +6 -33
  10. package/dist/diagnostic.d.ts.map +1 -1
  11. package/dist/diagnostic.js +71 -0
  12. package/dist/diagnostic.js.map +1 -1
  13. package/dist/dts-creator.d.ts +1 -3
  14. package/dist/dts-creator.d.ts.map +1 -1
  15. package/dist/dts-creator.js.map +1 -1
  16. package/dist/export-builder.d.ts +1 -14
  17. package/dist/export-builder.d.ts.map +1 -1
  18. package/dist/export-builder.js.map +1 -1
  19. package/dist/file.d.ts +5 -1
  20. package/dist/file.d.ts.map +1 -1
  21. package/dist/file.js +15 -0
  22. package/dist/file.js.map +1 -1
  23. package/dist/index.d.ts +7 -6
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +8 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/parser/at-import-parser.d.ts +1 -1
  28. package/dist/parser/at-import-parser.d.ts.map +1 -1
  29. package/dist/parser/at-value-parser.d.ts +2 -3
  30. package/dist/parser/at-value-parser.d.ts.map +1 -1
  31. package/dist/parser/at-value-parser.js +2 -13
  32. package/dist/parser/at-value-parser.js.map +1 -1
  33. package/dist/parser/css-module-parser.d.ts +2 -83
  34. package/dist/parser/css-module-parser.d.ts.map +1 -1
  35. package/dist/parser/css-module-parser.js +7 -8
  36. package/dist/parser/css-module-parser.js.map +1 -1
  37. package/dist/parser/rule-parser.d.ts +8 -4
  38. package/dist/parser/rule-parser.d.ts.map +1 -1
  39. package/dist/parser/rule-parser.js +9 -10
  40. package/dist/parser/rule-parser.js.map +1 -1
  41. package/dist/resolver.d.ts +3 -12
  42. package/dist/resolver.d.ts.map +1 -1
  43. package/dist/resolver.js +2 -2
  44. package/dist/resolver.js.map +1 -1
  45. package/dist/type.d.ts +174 -0
  46. package/dist/type.d.ts.map +1 -0
  47. package/dist/type.js +3 -0
  48. package/dist/type.js.map +1 -0
  49. package/dist/util.d.ts +1 -0
  50. package/dist/util.d.ts.map +1 -1
  51. package/dist/util.js +17 -0
  52. package/dist/util.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/checker.ts +13 -15
  55. package/src/config.ts +10 -12
  56. package/src/diagnostic.ts +69 -29
  57. package/src/dts-creator.ts +1 -3
  58. package/src/export-builder.ts +1 -16
  59. package/src/file.ts +17 -1
  60. package/src/index.ts +15 -13
  61. package/src/parser/at-import-parser.ts +1 -1
  62. package/src/parser/at-value-parser.ts +5 -17
  63. package/src/parser/css-module-parser.ts +16 -98
  64. package/src/parser/rule-parser.ts +17 -14
  65. package/src/resolver.ts +12 -15
  66. package/src/type.ts +191 -0
  67. package/src/util.ts +18 -0
  68. package/dist/parser/location.d.ts +0 -34
  69. package/dist/parser/location.d.ts.map +0 -1
  70. package/dist/parser/location.js +0 -9
  71. package/dist/parser/location.js.map +0 -1
  72. package/src/parser/location.ts +0 -40
package/src/checker.ts CHANGED
@@ -1,13 +1,13 @@
1
- import type { SemanticDiagnostic } from './diagnostic.js';
2
- import type { ExportBuilder } from './export-builder.js';
3
- import type { MatchesPattern } from './file.js';
4
1
  import type {
5
2
  AtValueTokenImporter,
6
3
  AtValueTokenImporterValue,
7
4
  CSSModule,
5
+ Diagnostic,
6
+ ExportBuilder,
7
+ MatchesPattern,
8
+ Resolver,
8
9
  TokenImporter,
9
- } from './parser/css-module-parser.js';
10
- import type { Resolver } from './resolver.js';
10
+ } from './type.js';
11
11
 
12
12
  export function checkCSSModule(
13
13
  cssModule: CSSModule,
@@ -15,8 +15,8 @@ export function checkCSSModule(
15
15
  matchesPattern: MatchesPattern,
16
16
  resolver: Resolver,
17
17
  getCSSModule: (path: string) => CSSModule | undefined,
18
- ): SemanticDiagnostic[] {
19
- const diagnostics: SemanticDiagnostic[] = [];
18
+ ): Diagnostic[] {
19
+ const diagnostics: Diagnostic[] = [];
20
20
 
21
21
  for (const tokenImporter of cssModule.tokenImporters) {
22
22
  const from = resolver(tokenImporter.from, { request: cssModule.fileName });
@@ -39,14 +39,13 @@ export function checkCSSModule(
39
39
  return diagnostics;
40
40
  }
41
41
 
42
- function createCannotImportModuleDiagnostic(cssModule: CSSModule, tokenImporter: TokenImporter): SemanticDiagnostic {
42
+ function createCannotImportModuleDiagnostic(cssModule: CSSModule, tokenImporter: TokenImporter): Diagnostic {
43
43
  return {
44
- type: 'semantic',
45
44
  text: `Cannot import module '${tokenImporter.from}'`,
46
45
  category: 'error',
47
- fileName: cssModule.fileName,
46
+ file: { fileName: cssModule.fileName, text: cssModule.text },
48
47
  start: { line: tokenImporter.fromLoc.start.line, column: tokenImporter.fromLoc.start.column },
49
- end: { line: tokenImporter.fromLoc.end.line, column: tokenImporter.fromLoc.end.column },
48
+ length: tokenImporter.fromLoc.end.offset - tokenImporter.fromLoc.start.offset,
50
49
  };
51
50
  }
52
51
 
@@ -54,13 +53,12 @@ function createModuleHasNoExportedTokenDiagnostic(
54
53
  cssModule: CSSModule,
55
54
  tokenImporter: AtValueTokenImporter,
56
55
  value: AtValueTokenImporterValue,
57
- ): SemanticDiagnostic {
56
+ ): Diagnostic {
58
57
  return {
59
- type: 'semantic',
60
58
  text: `Module '${tokenImporter.from}' has no exported token '${value.name}'.`,
61
59
  category: 'error',
62
- fileName: cssModule.fileName,
60
+ file: { fileName: cssModule.fileName, text: cssModule.text },
63
61
  start: { line: value.loc.start.line, column: value.loc.start.column },
64
- end: { line: value.loc.end.line, column: value.loc.end.column },
62
+ length: value.loc.end.offset - value.loc.start.offset,
65
63
  };
66
64
  }
package/src/config.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import ts from 'typescript';
2
- import type { SemanticDiagnostic } from './diagnostic.js';
3
2
  import { TsConfigFileNotFoundError } from './error.js';
4
3
  import { basename, dirname, join, resolve } from './path.js';
4
+ import type { Diagnostic } from './type.js';
5
5
 
6
6
  // https://github.com/microsoft/TypeScript/blob/caf1aee269d1660b4d2a8b555c2d602c97cb28d7/src/compiler/commandLineParser.ts#L3006
7
7
  const DEFAULT_INCLUDE_SPEC = '**/*';
@@ -60,7 +60,7 @@ export interface CMKConfig {
60
60
  configFileName: string;
61
61
  compilerOptions: ts.CompilerOptions;
62
62
  /** The diagnostics that occurred while reading the config file. */
63
- diagnostics: SemanticDiagnostic[];
63
+ diagnostics: Diagnostic[];
64
64
  }
65
65
 
66
66
  /**
@@ -79,7 +79,7 @@ interface UnnormalizedRawConfig {
79
79
  */
80
80
  interface ParsedRawData {
81
81
  config: UnnormalizedRawConfig;
82
- diagnostics: SemanticDiagnostic[];
82
+ diagnostics: Diagnostic[];
83
83
  }
84
84
 
85
85
  export function findTsConfigFile(project: string): string | undefined {
@@ -91,7 +91,7 @@ export function findTsConfigFile(project: string): string | undefined {
91
91
  return resolve(configFile);
92
92
  }
93
93
 
94
- function parseRawData(raw: unknown, configFileName: string): ParsedRawData {
94
+ function parseRawData(raw: unknown, tsConfigSourceFile: ts.TsConfigSourceFile): ParsedRawData {
95
95
  const result: ParsedRawData = {
96
96
  config: {
97
97
  includes: undefined,
@@ -126,10 +126,9 @@ function parseRawData(raw: unknown, configFileName: string): ParsedRawData {
126
126
  result.config.dtsOutDir = raw.cmkOptions.dtsOutDir;
127
127
  } else {
128
128
  result.diagnostics.push({
129
- type: 'semantic',
130
129
  category: 'error',
131
- text: '`dtsOutDir` must be a string.',
132
- fileName: configFileName,
130
+ text: `\`dtsOutDir\` in ${tsConfigSourceFile.fileName} must be a string.`,
131
+ // MEMO: Location information can be obtained from `tsConfigSourceFile.statements`, but this is complicated and will be omitted.
133
132
  });
134
133
  }
135
134
  }
@@ -138,10 +137,9 @@ function parseRawData(raw: unknown, configFileName: string): ParsedRawData {
138
137
  result.config.arbitraryExtensions = raw.cmkOptions.arbitraryExtensions;
139
138
  } else {
140
139
  result.diagnostics.push({
141
- type: 'semantic',
142
140
  category: 'error',
143
- text: '`arbitraryExtensions` must be a boolean.',
144
- fileName: configFileName,
141
+ text: `\`arbitraryExtensions\` in ${tsConfigSourceFile.fileName} must be a boolean.`,
142
+ // MEMO: Location information can be obtained from `tsConfigSourceFile.statements`, but this is complicated and will be omitted.
145
143
  });
146
144
  }
147
145
  }
@@ -168,7 +166,7 @@ export function readTsConfigFile(project: string): {
168
166
  configFileName: string;
169
167
  config: UnnormalizedRawConfig;
170
168
  compilerOptions: ts.CompilerOptions;
171
- diagnostics: SemanticDiagnostic[];
169
+ diagnostics: Diagnostic[];
172
170
  } {
173
171
  const configFileName = findTsConfigFile(project);
174
172
  if (!configFileName) throw new TsConfigFileNotFoundError();
@@ -195,7 +193,7 @@ export function readTsConfigFile(project: string): {
195
193
  ],
196
194
  );
197
195
  // Read options from `parsedCommandLine.raw`
198
- let parsedRawData = parseRawData(parsedCommandLine.raw, configFileName);
196
+ let parsedRawData = parseRawData(parsedCommandLine.raw, tsConfigSourceFile);
199
197
 
200
198
  // The options read from `parsedCommandLine.raw` do not inherit values from the file specified in `extends`.
201
199
  // So here we read the options from those files and merge them into `parsedRawData`.
package/src/diagnostic.ts CHANGED
@@ -1,37 +1,77 @@
1
- export type DiagnosticCategory = 'error' | 'warning';
1
+ import ts from 'typescript';
2
+ import type { SystemError } from './error.js';
3
+ import type { Diagnostic, DiagnosticSourceFile, DiagnosticWithLocation } from './type.js';
2
4
 
3
- export interface DiagnosticPosition {
4
- /** The line number in the source file. It is 1-based. */
5
- line: number;
6
- /** The column number in the source file. It is 1-based. */
7
- column: number;
8
- }
5
+ /** The error code used by tsserver to display the css-modules-kit error in the editor. */
6
+ const TS_ERROR_CODE = 0;
7
+
8
+ const TS_ERROR_SOURCE = 'css-modules-kit';
9
9
 
10
- export type Diagnostic = SemanticDiagnostic | SyntacticDiagnostic;
10
+ function convertErrorCategory(category: 'error' | 'warning' | 'suggestion'): ts.DiagnosticCategory {
11
+ switch (category) {
12
+ case 'error':
13
+ return ts.DiagnosticCategory.Error;
14
+ case 'warning':
15
+ return ts.DiagnosticCategory.Warning;
16
+ case 'suggestion':
17
+ return ts.DiagnosticCategory.Suggestion;
18
+ default:
19
+ throw new Error(`Unknown category: ${String(category)}`);
20
+ }
21
+ }
11
22
 
12
- interface DiagnosticBase {
13
- /** Text of diagnostic message. */
14
- text: string;
15
- /** The category of the diagnostic message. */
16
- category: DiagnosticCategory;
23
+ export function convertDiagnostic(
24
+ diagnostic: Diagnostic,
25
+ getSourceFile: (file: DiagnosticSourceFile) => ts.SourceFile,
26
+ ): ts.Diagnostic {
27
+ if ('file' in diagnostic) {
28
+ return convertDiagnosticWithLocation(diagnostic, getSourceFile);
29
+ } else {
30
+ return {
31
+ file: undefined,
32
+ start: undefined,
33
+ length: undefined,
34
+ category: convertErrorCategory(diagnostic.category),
35
+ messageText: diagnostic.text,
36
+ code: TS_ERROR_CODE,
37
+ source: TS_ERROR_SOURCE,
38
+ };
39
+ }
17
40
  }
18
41
 
19
- export interface SemanticDiagnostic extends DiagnosticBase {
20
- type: 'semantic';
21
- /** The filename of the file in which the diagnostic occurred */
22
- fileName?: string;
23
- /** Starting file position at which text applies. It is inclusive. */
24
- start?: DiagnosticPosition;
25
- /** The last file position at which the text applies. It is exclusive. */
26
- end?: DiagnosticPosition;
42
+ export function convertDiagnosticWithLocation(
43
+ diagnostic: DiagnosticWithLocation,
44
+ getSourceFile: (file: DiagnosticSourceFile) => ts.SourceFile,
45
+ ): ts.DiagnosticWithLocation {
46
+ const sourceFile = getSourceFile(diagnostic.file);
47
+ const start = ts.getPositionOfLineAndCharacter(sourceFile, diagnostic.start.line - 1, diagnostic.start.column - 1);
48
+ return {
49
+ file: sourceFile,
50
+ start,
51
+ length: diagnostic.length,
52
+ category: convertErrorCategory(diagnostic.category),
53
+ messageText: diagnostic.text,
54
+ code: TS_ERROR_CODE,
55
+ source: TS_ERROR_SOURCE,
56
+ };
27
57
  }
28
58
 
29
- export interface SyntacticDiagnostic extends DiagnosticBase {
30
- type: 'syntactic';
31
- /** The filename of the file in which the diagnostic occurred */
32
- fileName: string;
33
- /** Starting file position at which text applies. It is inclusive. */
34
- start: DiagnosticPosition;
35
- /** The last file position at which the text applies. It is exclusive. */
36
- end?: DiagnosticPosition;
59
+ export function convertSystemError(systemError: SystemError): ts.Diagnostic {
60
+ let messageText = systemError.message;
61
+ if (systemError.cause) {
62
+ if (systemError.cause instanceof Error) {
63
+ messageText += `: ${systemError.cause.message}`;
64
+ } else {
65
+ messageText += `: ${JSON.stringify(systemError.cause)}`;
66
+ }
67
+ }
68
+ return {
69
+ file: undefined,
70
+ start: undefined,
71
+ length: undefined,
72
+ category: ts.DiagnosticCategory.Error,
73
+ messageText,
74
+ code: TS_ERROR_CODE,
75
+ source: TS_ERROR_SOURCE,
76
+ };
37
77
  }
@@ -1,6 +1,4 @@
1
- import type { MatchesPattern } from './file.js';
2
- import type { CSSModule } from './parser/css-module-parser.js';
3
- import type { Resolver } from './resolver.js';
1
+ import type { CSSModule, MatchesPattern, Resolver } from './type.js';
4
2
 
5
3
  export const STYLES_EXPORT_NAME = 'styles';
6
4
 
@@ -1,6 +1,4 @@
1
- import type { MatchesPattern } from './file.js';
2
- import type { CSSModule } from './parser/css-module-parser.js';
3
- import type { Resolver } from './resolver.js';
1
+ import type { CSSModule, ExportBuilder, ExportRecord, MatchesPattern, Resolver } from './type.js';
4
2
 
5
3
  export interface ExportBuilderHost {
6
4
  matchesPattern: MatchesPattern;
@@ -8,19 +6,6 @@ export interface ExportBuilderHost {
8
6
  resolver: Resolver;
9
7
  }
10
8
 
11
- /**
12
- * The export token record of a CSS module.
13
- */
14
- export interface ExportRecord {
15
- /** The all exported tokens of the CSS module. */
16
- allTokens: string[];
17
- }
18
-
19
- export interface ExportBuilder {
20
- build(cssModule: CSSModule): ExportRecord;
21
- clearCache(): void;
22
- }
23
-
24
9
  /**
25
10
  * A builder for exported token records of CSS modules.
26
11
  */
package/src/file.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import ts from 'typescript';
2
2
  import { join, parse } from './path.js';
3
+ import type { MatchesPattern } from './type.js';
3
4
 
4
5
  export const CSS_MODULE_EXTENSION = '.module.css';
5
6
  const COMPONENT_EXTENSIONS = ['.tsx', '.jsx'];
@@ -36,7 +37,22 @@ export async function findComponentFile(
36
37
  return undefined;
37
38
  }
38
39
 
39
- export type MatchesPattern = (fileName: string) => boolean;
40
+ export function findComponentFileSync(
41
+ cssModuleFileName: string,
42
+ readFileSync: (path: string) => string,
43
+ ): { fileName: string; text: string } | undefined {
44
+ const pathWithoutExtension = cssModuleFileName.slice(0, -CSS_MODULE_EXTENSION.length);
45
+ for (const path of COMPONENT_EXTENSIONS.map((ext) => pathWithoutExtension + ext)) {
46
+ try {
47
+ // TODO: Cache the result of readFile
48
+ const text = readFileSync(path);
49
+ return { fileName: path, text };
50
+ } catch {
51
+ continue;
52
+ }
53
+ }
54
+ return undefined;
55
+ }
40
56
 
41
57
  /**
42
58
  * Create a function that checks whether the given file name matches the pattern.
package/src/index.ts CHANGED
@@ -1,38 +1,40 @@
1
1
  export type { CMKConfig } from './config.js';
2
2
  export { readConfigFile } from './config.js';
3
3
  export { TsConfigFileNotFoundError, SystemError } from './error.js';
4
+ export { parseCSSModule, type ParseCSSModuleOptions, type ParseCSSModuleResult } from './parser/css-module-parser.js';
5
+ export { parseRule } from './parser/rule-parser.js';
4
6
  export {
5
- parseCSSModule,
6
- type ParseCSSModuleOptions,
7
+ type Location,
8
+ type Position,
7
9
  type CSSModule,
8
10
  type Token,
9
11
  type AtImportTokenImporter,
10
12
  type TokenImporter,
11
13
  type AtValueTokenImporter,
12
14
  type AtValueTokenImporterValue,
13
- type ParseCSSModuleResult,
14
- } from './parser/css-module-parser.js';
15
- export { type Location, type Position } from './parser/location.js';
16
- export { parseRule } from './parser/rule-parser.js';
17
- export {
15
+ type Resolver,
16
+ type MatchesPattern,
17
+ type ExportBuilder,
18
+ type DiagnosticSourceFile,
18
19
  type Diagnostic,
19
- type SemanticDiagnostic,
20
- type SyntacticDiagnostic,
20
+ type DiagnosticWithLocation,
21
21
  type DiagnosticCategory,
22
22
  type DiagnosticPosition,
23
- } from './diagnostic.js';
23
+ } from './type.js';
24
24
  export { type CreateDtsOptions, createDts, STYLES_EXPORT_NAME } from './dts-creator.js';
25
- export { createResolver, type Resolver } from './resolver.js';
25
+ export { createResolver } from './resolver.js';
26
26
  export {
27
27
  CSS_MODULE_EXTENSION,
28
28
  getCssModuleFileName,
29
29
  isComponentFileName,
30
30
  isCSSModuleFile,
31
31
  findComponentFile,
32
- type MatchesPattern,
32
+ findComponentFileSync,
33
33
  createMatchesPattern,
34
34
  getFileNamesByPattern,
35
35
  } from './file.js';
36
36
  export { checkCSSModule } from './checker.js';
37
- export { type ExportBuilder, createExportBuilder } from './export-builder.js';
37
+ export { createExportBuilder } from './export-builder.js';
38
38
  export { join, resolve, relative, dirname, basename, parse, matchesGlob, isAbsolute } from './path.js';
39
+ export { findUsedTokenNames } from './util.js';
40
+ export { convertDiagnostic, convertDiagnosticWithLocation, convertSystemError } from './diagnostic.js';
@@ -1,6 +1,6 @@
1
1
  import type { AtRule } from 'postcss';
2
2
  import postcssValueParser from 'postcss-value-parser';
3
- import type { Location } from './location.js';
3
+ import type { Location } from '../type.js';
4
4
 
5
5
  interface ParsedAtImport {
6
6
  from: string;
@@ -1,6 +1,5 @@
1
1
  import type { AtRule } from 'postcss';
2
- import type { DiagnosticPosition, SyntacticDiagnostic } from '../diagnostic.js';
3
- import type { Location } from './location.js';
2
+ import type { DiagnosticPosition, DiagnosticWithDetachedLocation, Location } from '../type.js';
4
3
 
5
4
  interface ValueDeclaration {
6
5
  type: 'valueDeclaration';
@@ -25,7 +24,7 @@ type ParsedAtValue = ValueDeclaration | ValueImportDeclaration;
25
24
 
26
25
  interface ParseAtValueResult {
27
26
  atValue?: ParsedAtValue;
28
- diagnostics: SyntacticDiagnostic[];
27
+ diagnostics: DiagnosticWithDetachedLocation[];
29
28
  }
30
29
 
31
30
  const VALUE_IMPORT_PATTERN = /^(.+?)\s+from\s+("[^"]*"|'[^']*')$/du;
@@ -53,7 +52,7 @@ const IMPORTED_ITEM_PATTERN = /^([\w-]+)(?:\s+as\s+([\w-]+))?/du;
53
52
  // MEMO: css-modules-kit does not support `@value` with variable module name (e.g., `@value a from moduleName;`) to simplify the implementation.
54
53
  export function parseAtValue(atValue: AtRule): ParseAtValueResult {
55
54
  const matchesForValueImport = atValue.params.match(VALUE_IMPORT_PATTERN);
56
- const diagnostics: SyntacticDiagnostic[] = [];
55
+ const diagnostics: DiagnosticWithDetachedLocation[] = [];
57
56
  if (matchesForValueImport) {
58
57
  const [, importedItems, from] = matchesForValueImport as [string, string, string];
59
58
  // The length of the `@value ` part in `@value import1 from '...'`
@@ -101,15 +100,9 @@ export function parseAtValue(atValue: AtRule): ParseAtValueResult {
101
100
  line: atValue.source!.start!.line,
102
101
  column: atValue.source!.start!.column + baseLength + currentItemIndex,
103
102
  };
104
- const end: DiagnosticPosition = {
105
- line: start.line,
106
- column: start.column + alias.length,
107
- };
108
103
  diagnostics.push({
109
- type: 'syntactic',
110
- fileName: atValue.source!.input.file!,
111
104
  start,
112
- end,
105
+ length: alias.length,
113
106
  text: `\`${alias}\` is invalid syntax.`,
114
107
  category: 'error',
115
108
  });
@@ -160,16 +153,11 @@ export function parseAtValue(atValue: AtRule): ParseAtValueResult {
160
153
  return { atValue: parsedAtValue, diagnostics };
161
154
  }
162
155
  diagnostics.push({
163
- type: 'syntactic',
164
- fileName: atValue.source!.input.file!,
165
156
  start: {
166
157
  line: atValue.source!.start!.line,
167
158
  column: atValue.source!.start!.column,
168
159
  },
169
- end: {
170
- line: atValue.source!.end!.line,
171
- column: atValue.source!.end!.column + 1,
172
- },
160
+ length: atValue.source!.end!.offset - atValue.source!.start!.offset,
173
161
  text: `\`${atValue.toString()}\` is a invalid syntax.`,
174
162
  category: 'error',
175
163
  });
@@ -1,10 +1,15 @@
1
1
  import type { AtRule, Node, Root, Rule } from 'postcss';
2
2
  import { CssSyntaxError, parse } from 'postcss';
3
3
  import safeParser from 'postcss-safe-parser';
4
- import type { SyntacticDiagnostic } from '../diagnostic.js';
4
+ import type {
5
+ CSSModule,
6
+ DiagnosticWithDetachedLocation,
7
+ DiagnosticWithLocation,
8
+ Token,
9
+ TokenImporter,
10
+ } from '../type.js';
5
11
  import { parseAtImport } from './at-import-parser.js';
6
12
  import { parseAtValue } from './at-value-parser.js';
7
- import { type Location } from './location.js';
8
13
  import { parseRule } from './rule-parser.js';
9
14
 
10
15
  type AtImport = AtRule & { name: 'import' };
@@ -30,7 +35,7 @@ function isRuleNode(node: Node): node is Rule {
30
35
  * Collect tokens from the AST.
31
36
  */
32
37
  function collectTokens(ast: Root) {
33
- const allDiagnostics: SyntacticDiagnostic[] = [];
38
+ const allDiagnostics: DiagnosticWithDetachedLocation[] = [];
34
39
  const localTokens: Token[] = [];
35
40
  const tokenImporters: TokenImporter[] = [];
36
41
  ast.walk((node) => {
@@ -59,92 +64,6 @@ function collectTokens(ast: Root) {
59
64
  return { localTokens, tokenImporters, diagnostics: allDiagnostics };
60
65
  }
61
66
 
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
67
  export interface ParseCSSModuleOptions {
149
68
  fileName: string;
150
69
  safe: boolean;
@@ -152,11 +71,12 @@ export interface ParseCSSModuleOptions {
152
71
 
153
72
  export interface ParseCSSModuleResult {
154
73
  cssModule: CSSModule;
155
- diagnostics: SyntacticDiagnostic[];
74
+ diagnostics: DiagnosticWithLocation[];
156
75
  }
157
76
 
158
77
  export function parseCSSModule(text: string, { fileName, safe }: ParseCSSModuleOptions): ParseCSSModuleResult {
159
78
  let ast: Root;
79
+ const diagnosticSourceFile = { fileName, text };
160
80
  try {
161
81
  const parser = safe ? safeParser : parse;
162
82
  ast = parser(text, { from: fileName });
@@ -164,16 +84,13 @@ export function parseCSSModule(text: string, { fileName, safe }: ParseCSSModuleO
164
84
  if (e instanceof CssSyntaxError) {
165
85
  const start = { line: e.line ?? 1, column: e.column ?? 1 };
166
86
  return {
167
- cssModule: { fileName, localTokens: [], tokenImporters: [] },
87
+ cssModule: { fileName, text, localTokens: [], tokenImporters: [] },
168
88
  diagnostics: [
169
89
  {
170
- type: 'syntactic',
171
- fileName,
90
+ file: diagnosticSourceFile,
172
91
  start,
173
- ...(e.endLine !== undefined &&
174
- e.endColumn !== undefined && {
175
- end: { line: e.endLine, column: e.endColumn },
176
- }),
92
+ // TODO: Assign correct length (e.g. `e.endOffset - e.offset`)
93
+ length: 1,
177
94
  text: e.reason,
178
95
  category: 'error',
179
96
  },
@@ -185,8 +102,9 @@ export function parseCSSModule(text: string, { fileName, safe }: ParseCSSModuleO
185
102
  const { localTokens, tokenImporters, diagnostics } = collectTokens(ast);
186
103
  const cssModule = {
187
104
  fileName,
105
+ text,
188
106
  localTokens,
189
107
  tokenImporters,
190
108
  };
191
- return { cssModule, diagnostics };
109
+ return { cssModule, diagnostics: diagnostics.map((diagnostic) => ({ ...diagnostic, file: diagnosticSourceFile })) };
192
110
  }