Skip to content

Commit 59c3422

Browse files
kohlmannj-nytskovy
authored andcommitted
feat: add --allowArbitraryExtensions option (compatible with the TS 5.0 feature of the same name)
1 parent c60f74e commit 59c3422

10 files changed

+86
-3
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,16 @@ Define a [single custom SASS importer or an array of SASS importers](https://git
352352

353353
Refer to [`lib/sass/importer.ts`](/blob/master/lib/sass/importer.ts) for more details and the `node-sass` and `sass` importer type definitions.
354354

355+
### `--allowArbitraryExtensions`
356+
357+
- **Type**: `boolean`
358+
- **Default**: `false`
359+
- **Example**: `typed-scss-modules src --allowArbitraryExtensions`
360+
361+
Output filenames that will be compatible with the "arbitrary file extensions" feature that was introduced in TypeScript 5.0. See [the docs](https://www.typescriptlang.org/tsconfig#allowArbitraryExtensions) for more info.
362+
363+
In essence, the `*.scss.d.ts` extension now becomes `*.d.scss.ts` so that you can import CSS modules in projects using ESM module resolution.
364+
355365
## Examples
356366

357367
For examples of how this tool can be used and configured, see the `examples` directory:

__tests__/core/generate.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describeAllImplementations((implementation) => {
2727
updateStaleOnly: false,
2828
logLevel: "verbose",
2929
outputFolder: null,
30+
allowArbitraryExtensions: false,
3031
});
3132

3233
expect(fs.writeFileSync).toHaveBeenCalledTimes(6);

__tests__/core/list-different.test.ts

+5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ describeAllImplementations((implementation) => {
3838
updateStaleOnly: false,
3939
logLevel: "verbose",
4040
outputFolder: null,
41+
allowArbitraryExtensions: false,
4142
});
4243

4344
expect(exit).toHaveBeenCalledWith(1);
@@ -67,6 +68,7 @@ describeAllImplementations((implementation) => {
6768
logLevel: "verbose",
6869
nameFormat: ["kebab"],
6970
outputFolder: null,
71+
allowArbitraryExtensions: false,
7072
});
7173

7274
expect(console.log).toHaveBeenCalledTimes(1);
@@ -93,6 +95,7 @@ describeAllImplementations((implementation) => {
9395
updateStaleOnly: false,
9496
logLevel: "verbose",
9597
outputFolder: null,
98+
allowArbitraryExtensions: false,
9699
});
97100

98101
expect(exit).not.toHaveBeenCalled();
@@ -116,6 +119,7 @@ describeAllImplementations((implementation) => {
116119
updateStaleOnly: false,
117120
logLevel: "verbose",
118121
outputFolder: null,
122+
allowArbitraryExtensions: false,
119123
});
120124

121125
expect(exit).toHaveBeenCalledWith(1);
@@ -146,6 +150,7 @@ describeAllImplementations((implementation) => {
146150
updateStaleOnly: false,
147151
logLevel: "verbose",
148152
outputFolder: null,
153+
allowArbitraryExtensions: false,
149154
});
150155

151156
expect(exit).not.toHaveBeenCalled();

__tests__/core/list-files-and-perform-sanity-check.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const options: ConfigOptions = {
1515
updateStaleOnly: false,
1616
logLevel: "verbose",
1717
outputFolder: null,
18+
allowArbitraryExtensions: false,
1819
};
1920

2021
describe("listAllFilesAndPerformSanityCheck", () => {

__tests__/core/write-file.test.ts

+42
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ describeAllImplementations((implementation) => {
3535
updateStaleOnly: false,
3636
logLevel: "verbose",
3737
outputFolder: null,
38+
allowArbitraryExtensions: false,
3839
});
3940

4041
const expectedPath = path.join(
@@ -51,6 +52,40 @@ describeAllImplementations((implementation) => {
5152
);
5253
});
5354

55+
it("writes the corresponding type definitions for a file and logs when allowArbitraryExtensions is set", async () => {
56+
const testFile = path.resolve(__dirname, "..", "dummy-styles/style.scss");
57+
58+
await writeFile(testFile, {
59+
banner: "",
60+
watch: false,
61+
ignoreInitial: false,
62+
exportType: "named",
63+
exportTypeName: "ClassNames",
64+
exportTypeInterface: "Styles",
65+
listDifferent: false,
66+
ignore: [],
67+
implementation,
68+
quoteType: "single",
69+
updateStaleOnly: false,
70+
logLevel: "verbose",
71+
outputFolder: null,
72+
allowArbitraryExtensions: true,
73+
});
74+
75+
const expectedPath = path.join(
76+
process.cwd(),
77+
"__tests__/dummy-styles/style.d.scss.ts"
78+
);
79+
80+
expect(fs.writeFileSync).toHaveBeenCalledWith(
81+
expectedPath,
82+
"export declare const someClass: string;\n"
83+
);
84+
expect(console.log).toHaveBeenCalledWith(
85+
expect.stringContaining(`[GENERATED TYPES] ${expectedPath}`)
86+
);
87+
});
88+
5489
it("skips files with no classes", async () => {
5590
const testFile = path.resolve(__dirname, "..", "dummy-styles/empty.scss");
5691

@@ -68,6 +103,7 @@ describeAllImplementations((implementation) => {
68103
updateStaleOnly: false,
69104
logLevel: "verbose",
70105
outputFolder: null,
106+
allowArbitraryExtensions: false,
71107
});
72108

73109
expect(fs.writeFileSync).not.toHaveBeenCalled();
@@ -111,6 +147,7 @@ describeAllImplementations((implementation) => {
111147
updateStaleOnly: false,
112148
logLevel: "verbose",
113149
outputFolder: null,
150+
allowArbitraryExtensions: false,
114151
});
115152

116153
expect(fs.unlinkSync).toHaveBeenCalledWith(existingTypes);
@@ -142,6 +179,7 @@ describeAllImplementations((implementation) => {
142179
updateStaleOnly: false,
143180
logLevel: "verbose",
144181
outputFolder: "__generated__",
182+
allowArbitraryExtensions: false,
145183
});
146184

147185
const expectedPath = path.join(
@@ -200,6 +238,7 @@ describeAllImplementations((implementation) => {
200238
updateStaleOnly: true,
201239
logLevel: "verbose",
202240
outputFolder: null,
241+
allowArbitraryExtensions: false,
203242
});
204243

205244
expect(fs.writeFileSync).not.toHaveBeenCalled();
@@ -235,6 +274,7 @@ describeAllImplementations((implementation) => {
235274
updateStaleOnly: true,
236275
logLevel: "verbose",
237276
outputFolder: null,
277+
allowArbitraryExtensions: false,
238278
});
239279

240280
expect(fs.writeFileSync).toHaveBeenCalled();
@@ -259,6 +299,7 @@ describeAllImplementations((implementation) => {
259299
updateStaleOnly: true,
260300
logLevel: "verbose",
261301
outputFolder: null,
302+
allowArbitraryExtensions: false,
262303
});
263304

264305
expect(fs.writeFileSync).not.toHaveBeenCalled();
@@ -281,6 +322,7 @@ describeAllImplementations((implementation) => {
281322
updateStaleOnly: true,
282323
logLevel: "verbose",
283324
outputFolder: null,
325+
allowArbitraryExtensions: false,
284326
});
285327

286328
expect(fs.statSync).not.toHaveBeenCalledWith(testFile);

__tests__/load.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ describe("#mergeOptions", () => {
5151
logLevel: "silent",
5252
outputFolder: "__generated__",
5353
banner: "// override",
54+
allowArbitraryExtensions: true,
5455
},
5556
{}
5657
)
@@ -69,6 +70,7 @@ describe("#mergeOptions", () => {
6970
logLevel: "silent",
7071
outputFolder: "__generated__",
7172
banner: "// override",
73+
allowArbitraryExtensions: true,
7274
});
7375
});
7476

@@ -94,6 +96,7 @@ describe("#mergeOptions", () => {
9496
banner: "// override",
9597
outputFolder: "__generated__",
9698
importer,
99+
allowArbitraryExtensions: true,
97100
}
98101
)
99102
).toEqual({
@@ -112,6 +115,7 @@ describe("#mergeOptions", () => {
112115
banner: "// override",
113116
outputFolder: "__generated__",
114117
importer,
118+
allowArbitraryExtensions: true,
115119
});
116120
});
117121

@@ -135,6 +139,7 @@ describe("#mergeOptions", () => {
135139
logLevel: "silent",
136140
banner: "// override",
137141
outputFolder: "__cli-generated__",
142+
allowArbitraryExtensions: true,
138143
},
139144
{
140145
nameFormat: ["param"],
@@ -170,6 +175,7 @@ describe("#mergeOptions", () => {
170175
banner: "// override",
171176
outputFolder: "__cli-generated__",
172177
importer,
178+
allowArbitraryExtensions: true,
173179
});
174180
});
175181

@@ -195,6 +201,7 @@ describe("#mergeOptions", () => {
195201
logLevel: "silent",
196202
banner: undefined,
197203
outputFolder: "__cli-generated__",
204+
allowArbitraryExtensions: true,
198205
},
199206
{
200207
aliases: {},
@@ -214,6 +221,7 @@ describe("#mergeOptions", () => {
214221
banner: "// banner",
215222
outputFolder: "__generated__",
216223
importer,
224+
allowArbitraryExtensions: false,
217225
}
218226
)
219227
).toEqual({
@@ -234,6 +242,7 @@ describe("#mergeOptions", () => {
234242
banner: "// banner",
235243
outputFolder: "__cli-generated__",
236244
importer,
245+
allowArbitraryExtensions: true,
237246
});
238247
});
239248
});

lib/cli.ts

+5
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ const { _: patterns, ...rest } = yargs
142142
describe:
143143
"Inserts text at the top of every output file for documentation purposes.",
144144
})
145+
.options("allowArbitraryExtensions", {
146+
boolean: true,
147+
describe:
148+
'Output filenames that will be compatible with the "arbitrary file extensions" feature that was introduced in TypeScript 5.0.',
149+
})
145150
.parseSync();
146151

147152
// eslint-disable-next-line @typescript-eslint/no-floating-promises

lib/core/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface CLIOptions extends Exclude<SASSOptions, CLIOnlyOptions> {
1616
watch: boolean;
1717
logLevel: LogLevel;
1818
outputFolder: string | null;
19+
allowArbitraryExtensions: boolean;
1920
}
2021

2122
export interface ConfigOptions extends CLIOptions, SASSOptions {}

lib/load.ts

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export const DEFAULT_OPTIONS: CLIOptions = {
7979
logLevel: logLevelDefault,
8080
banner: bannerTypeDefault,
8181
outputFolder: null,
82+
allowArbitraryExtensions: false,
8283
};
8384

8485
const removedUndefinedValues = <Obj extends Record<string, unknown>>(

lib/typescript/get-type-definition-path.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,24 @@ export const getTypeDefinitionPath = (
1313
file: string,
1414
options: ConfigOptions
1515
): string => {
16+
let resolvedPath = file;
17+
1618
if (options.outputFolder) {
1719
const relativePath = path.relative(CURRENT_WORKING_DIRECTORY, file);
18-
const resolvedPath = path.resolve(
20+
resolvedPath = path.resolve(
1921
CURRENT_WORKING_DIRECTORY,
2022
options.outputFolder,
2123
relativePath
2224
);
25+
}
2326

24-
return `${resolvedPath}.d.ts`;
27+
if (options.allowArbitraryExtensions) {
28+
const resolvedDirname = path.dirname(resolvedPath);
29+
// Note: `ext` includes a leading period (e.g. '.scss')
30+
const { name, ext } = path.parse(resolvedPath);
31+
// @see https://www.typescriptlang.org/tsconfig/#allowArbitraryExtensions
32+
return path.join(resolvedDirname, `${name}.d${ext}.ts`);
2533
} else {
26-
return `${file}.d.ts`;
34+
return `${resolvedPath}.d.ts`;
2735
}
2836
};

0 commit comments

Comments
 (0)