Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix CI #241

Merged
merged 15 commits into from
May 11, 2024
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"jest.jestCommandLine": "npm run test --",
// It is expensive to run the test each time the file to be tested (e.g. `loader.ts`) is saved.
Expand Down
14 changes: 13 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FlatCompat } from '@eslint/eslintrc';
// @ts-expect-error
import js from '@eslint/js';

// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = new URL('.', import.meta.url).pathname;

const compat = new FlatCompat({
Expand All @@ -16,7 +17,15 @@ const compat = new FlatCompat({
/** @type {import('eslint').Linter.FlatConfig[]} */
// eslint-disable-next-line import/no-default-export
export default [
{ ignores: ['**/dist', '**/*.css.d.ts', 'packages/example', 'docs/how-does-definition-jumps-work'] },
{
ignores: [
'**/dist',
'**/*.css.d.ts',
'packages/example',
'docs/how-does-definition-jumps-work',
'packages/happy-css-modules/bin/**',
],
},
// NOTE: This is a hack that allows eslint-plugin-import to work with flat config.
// ref: https://github.com/import-js/eslint-plugin-import/issues/2556#issuecomment-1419518561
{
Expand Down Expand Up @@ -57,7 +66,9 @@ export default [
// ],
// },
// ],
'import/no-extraneous-dependencies': 'off',
'no-console': 2,
'no-use-before-define': 0,
},
},
{
Expand All @@ -70,6 +81,7 @@ export default [
rules: {
'@typescript-eslint/no-unused-vars': [2, { argsIgnorePattern: '^_' }],
'@typescript-eslint/consistent-type-imports': [2, { disallowTypeAnnotations: false }],
'@typescript-eslint/no-non-null-assertion': 0,
},
},
...compat.env({
Expand Down
4 changes: 2 additions & 2 deletions jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FIXTURE_DIR_PATH } from './packages/happy-css-modules/src/test-util/uti
const jsonSerializer: jest.SnapshotSerializerPlugin = {
serialize(val) {
const json = JSON.stringify(val);
const replacedJson = json.replace(new RegExp(FIXTURE_DIR_PATH, 'g'), '<fixtures>');
const replacedJson = json.replace(new RegExp(FIXTURE_DIR_PATH, 'gu'), '<fixtures>');
return format(replacedJson, { parser: 'json5', printWidth: 120 }).trimEnd();
},

Expand Down Expand Up @@ -41,7 +41,7 @@ const jsonSerializer: jest.SnapshotSerializerPlugin = {
const errorSerializer: jest.SnapshotSerializerPlugin = {
serialize(val) {
if (!(val instanceof Error)) throw new Error('unreachable');
return val.message.replace(new RegExp(FIXTURE_DIR_PATH, 'g'), '<fixtures>');
return val.message.replace(new RegExp(FIXTURE_DIR_PATH, 'gu'), '<fixtures>');
},

test(val) {
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@typescript-eslint/parser": "^5.31.0",
"@typescript/server-harness": "^0.2.0",
"dedent": "^1.5.1",
"eslint": "^8.20.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.26.0",
"jest": "^29.0.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-css-modules/src/emitter/dts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe('generateDtsContentWithSourceMap', () => {
.foo_bar {}
`,
});
return await locator.load(filePath);
return locator.load(filePath);
}
test('undefined', async () => {
const result = await getResult(filePath);
Expand Down
15 changes: 8 additions & 7 deletions packages/happy-css-modules/src/emitter/dts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import { getRelativePath, type DtsFormatOptions } from './index.js';
export function getDtsFilePath(filePath: string, arbitraryExtensions: boolean): string {
if (arbitraryExtensions) {
const { dir, name, ext } = parse(filePath);
return join(dir, name + '.d' + ext + '.ts');
return join(dir, `${name}.d${ext}.ts`);
} else {
return filePath + '.d.ts';
return `${filePath}.d.ts`;
}
}

function dashesCamelCase(str: string): string {
return str.replace(/-+(\w)/g, function (match, firstLetter) {
return str.replace(/-+(\w)/gu, (match, firstLetter) => {
return firstLetter.toUpperCase();
});
}
Expand Down Expand Up @@ -66,7 +66,7 @@ function generateTokenDeclarations(
if (originalLocation.filePath === undefined) {
// If the original location is not specified, fallback to the source file.
originalLocation = {
filePath: filePath,
filePath,
start: { line: 1, column: 1 },
end: { line: 1, column: 1 },
};
Expand Down Expand Up @@ -101,6 +101,7 @@ function generateTokenDeclarations(
return result;
}

// eslint-disable-next-line max-params
export function generateDtsContentWithSourceMap(
filePath: string,
dtsFilePath: string,
Expand All @@ -122,10 +123,10 @@ export function generateDtsContentWithSourceMap(
sourceNode = new SourceNode(null, null, null, '');
} else {
sourceNode = new SourceNode(1, 0, getRelativePath(sourceMapFilePath, filePath), [
'declare const styles:' + EOL,
`declare const styles:${EOL}`,
...tokenDeclarations.map((tokenDeclaration) => [' ', tokenDeclaration, EOL]),
';' + EOL,
'export default styles;' + EOL,
`;${EOL}`,
`export default styles;${EOL}`,
]);
}
const codeWithSourceMap = sourceNode.toStringWithSourceMap({
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-css-modules/src/emitter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function getRelativePath(fromFilePath: string, toFilePath: string): strin
if (resolved.startsWith('..')) {
return resolved;
} else {
return './' + resolved;
return `./${resolved}`;
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/happy-css-modules/src/emitter/source-map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ test('getSourceMapFilePath', () => {

test('generateSourceMappingURLComment', () => {
expect(generateSourceMappingURLComment('/app/src/dir/1.css.d.ts', '/app/src/dir/1.css.d.ts.map')).toBe(
'//# sourceMappingURL=./1.css.d.ts.map' + EOL,
`//# sourceMappingURL=./1.css.d.ts.map${EOL}`,
);
expect(generateSourceMappingURLComment('/app/src/dir1/1.css.d.ts', '/app/src/dir2/1.css.d.ts.map')).toBe(
'//# sourceMappingURL=../dir2/1.css.d.ts.map' + EOL,
`//# sourceMappingURL=../dir2/1.css.d.ts.map${EOL}`,
);
});
4 changes: 2 additions & 2 deletions packages/happy-css-modules/src/emitter/source-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { getRelativePath } from './index.js';
* @returns The path to the .d.ts.map file. It is absolute.
*/
export function getSourceMapFilePath(filePath: string, arbitraryExtensions: boolean): string {
return getDtsFilePath(filePath, arbitraryExtensions) + '.map';
return `${getDtsFilePath(filePath, arbitraryExtensions)}.map`;
}

export function generateSourceMappingURLComment(dtsFilePath: string, sourceMapFilePath: string): string {
return `//# sourceMappingURL=${getRelativePath(dtsFilePath, sourceMapFilePath)}` + EOL;
return `//# sourceMappingURL=${getRelativePath(dtsFilePath, sourceMapFilePath)}${EOL}`;
}
7 changes: 6 additions & 1 deletion packages/happy-css-modules/src/locator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export class Locator {
for (const dependency of dependencies) {
const entry = this.cache.get(dependency);
if (!entry) return true;
// eslint-disable-next-line no-await-in-loop
const mtime = (await stat(dependency)).mtime.getTime();
if (entry.mtime !== mtime) return true;
}
Expand All @@ -124,7 +125,7 @@ export class Locator {
dependencies: result.dependencies
.map((dep) => {
if (typeof dep === 'string') return dep;
if (dep.protocol !== 'file:') throw new Error('Unsupported protocol: ' + dep.protocol);
if (dep.protocol !== 'file:') throw new Error(`Unsupported protocol: ${dep.protocol}`);
return dep.pathname;
})
.filter((dep) => {
Expand Down Expand Up @@ -171,7 +172,9 @@ export class Locator {
const importedSheetPath = parseAtImport(atImport);
if (!importedSheetPath) continue;
if (isIgnoredSpecifier(importedSheetPath)) continue;
// eslint-disable-next-line no-await-in-loop
const from = await this.resolver(importedSheetPath, { request: filePath });
// eslint-disable-next-line no-await-in-loop
const result = await this._load(from);
const externalTokens = result.tokens;
dependencies.push(from, ...result.dependencies);
Expand All @@ -197,7 +200,9 @@ export class Locator {
const declarationDetail = parseComposesDeclarationWithFromUrl(composesDeclaration);
if (!declarationDetail) continue;
if (isIgnoredSpecifier(declarationDetail.from)) continue;
// eslint-disable-next-line no-await-in-loop
const from = await this.resolver(declarationDetail.from, { request: filePath });
// eslint-disable-next-line no-await-in-loop
const result = await this._load(from);
const externalTokens = result.tokens.filter((token) => declarationDetail.tokenNames.includes(token.name));
dependencies.push(from, ...result.dependencies);
Expand Down
4 changes: 3 additions & 1 deletion packages/happy-css-modules/src/resolver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type ResolverOptions = {
/**
* The function to resolve the path of the imported file.
* @returns The resolved path of the imported file. `false` means to skip resolving.
* */
*/
export type Resolver = (specifier: string, options: ResolverOptions) => string | false | Promise<string | false>;

export type DefaultResolverOptions = WebpackResolverOptions;
Expand Down Expand Up @@ -43,8 +43,10 @@ export const createDefaultResolver: (defaultResolverOptions?: DefaultResolverOpt
return async (specifier, options) => {
for (const resolver of resolvers) {
try {
// eslint-disable-next-line no-await-in-loop
const resolved = await resolver(specifier, options);
if (resolved !== false) {
// eslint-disable-next-line no-await-in-loop
const isExists = await exists(resolved);
if (isExists) return resolved;
}
Expand Down
6 changes: 4 additions & 2 deletions packages/happy-css-modules/src/resolver/webpack-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
mainFields: ['sass', 'style', 'main', '...'],
mainFiles: ['_index', 'index', '...'],
extensions: ['.sass', '.scss', '.css'],
restrictions: [/\.((sa|sc|c)ss)$/i],
restrictions: [/\.((sa|sc|c)ss)$/iu],
preferRelative: true,
alias: webpackResolveAlias,
modules: ['node_modules', ...(sassLoadPaths ?? [])],
Expand Down Expand Up @@ -96,6 +96,7 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
// ref: https://github.com/webpack-contrib/css-loader/blob/5e6cf91fd3f0c8b5fb4b91197b98dc56abdef4bf/src/utils.js#L92-L95
// ref: https://github.com/webpack-contrib/sass-loader/blob/49a578a218574ddc92a597c7e365b6c21960717e/src/utils.js#L368-L370
// ref: https://github.com/webpack-contrib/less-loader/blob/d74f740c100c4006b00dfb3e02c6d5aaf8713519/src/utils.js#L72-L75
// eslint-disable-next-line no-param-reassign
if (specifier.startsWith('~')) specifier = specifier.slice(1);

for (const resolver of resolvers) {
Expand All @@ -104,13 +105,14 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
? // Support partial import for sass
// https://sass-lang.com/documentation/at-rules/import#partials
// https://github.com/webpack-contrib/sass-loader/blob/0e9494074f69a6b6d47efea6c083a02a31a5ae84/test/sass/import-with-underscore.sass
[join(dirname(specifier), '_' + basename(specifier)), specifier]
[join(dirname(specifier), `_${basename(specifier)}`), specifier]
: [specifier];

for (const specifierVariant of specifierVariants) {
try {
const resolved = resolver(dirname(options.request), specifierVariant);
if (resolved !== false) {
// eslint-disable-next-line no-await-in-loop
const isExists = await exists(resolved);
if (isExists) return resolved;
}
Expand Down
11 changes: 6 additions & 5 deletions packages/happy-css-modules/src/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ test('uses cache in watch mode', async () => {
await waitForAsyncTask(500); // Wait until the file is written

// The updated 1.css will be processed, and the non-updated 2.css will be skipped.
// eslint-disable-next-line require-atomic-updates
watcher = await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true, watch: true });
await waitForAsyncTask(1000); // Wait until the watcher is ready
expect(consoleLogSpy).toBeCalledTimes(3);
Expand Down Expand Up @@ -187,7 +188,7 @@ test('does not emit declaration map if declarationMap is false', async () => {
});
await run({ ...defaultOptions, declarationMap: false });
await expect(readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).resolves.not.toThrow();
await expect(readFile(getFixturePath('/test/1.css.d.ts.map'), 'utf8')).rejects.toThrow(/ENOENT/);
await expect(readFile(getFixturePath('/test/1.css.d.ts.map'), 'utf8')).rejects.toThrow(/ENOENT/u);
});
test('supports transformer', async () => {
createFixtures({
Expand All @@ -205,17 +206,17 @@ test('watches for changes in files', async () => {

await writeFile(getFixturePath('/test/1.css'), '.a-1 {}');
await waitForAsyncTask(500); // Wait until the file is written
expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-1/);
expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-1/u);

await writeFile(getFixturePath('/test/1.css'), '.a-2 {}');
await waitForAsyncTask(500); // Wait until the file is written
expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-2/);
expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-2/u);

await writeFile(getFixturePath('/test/2.css'), '.b {}');
await writeFile(getFixturePath('/test/3.css'), '.c {}');
await waitForAsyncTask(500); // Wait until the file is written
expect(await readFile(getFixturePath('/test/2.css.d.ts'), 'utf8')).toMatch(/b/);
expect(await readFile(getFixturePath('/test/3.css.d.ts'), 'utf8')).toMatch(/c/);
expect(await readFile(getFixturePath('/test/2.css.d.ts'), 'utf8')).toMatch(/b/u);
expect(await readFile(getFixturePath('/test/3.css.d.ts'), 'utf8')).toMatch(/c/u);
});
test('returns an error if the file fails to process in non-watch mode', async () => {
createFixtures({
Expand Down
9 changes: 6 additions & 3 deletions packages/happy-css-modules/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type OverrideProp<T, K extends keyof T, V extends T[K]> = Omit<T, K> & { [P in K
export async function run(options: OverrideProp<RunnerOptions, 'watch', true>): Promise<Watcher>;
export async function run(options: RunnerOptions): Promise<void>;
export async function run(options: RunnerOptions): Promise<Watcher | void> {
// eslint-disable-next-line new-cap
const lock = new AwaitLock.default();
const logger = new Logger(options.logLevel ?? 'info');

Expand Down Expand Up @@ -128,6 +129,7 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
async function processFile(filePath: string) {
async function isChangedFile(filePath: string) {
const result = await cache.getAndUpdateCache(filePath);
// eslint-disable-next-line @typescript-eslint/no-throw-literal
if (result.error) throw result.error;
return result.changed;
}
Expand Down Expand Up @@ -175,6 +177,7 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {

const errors: unknown[] = [];
for (const filePath of filePaths) {
// eslint-disable-next-line no-await-in-loop
await processFile(filePath).catch((e) => errors.push(e));
}

Expand All @@ -184,13 +187,13 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
}

if (!options.watch) {
logger.info('Generate .d.ts for ' + options.pattern + '...');
logger.info(`Generate .d.ts for ${options.pattern}...`);
await processAllFiles();
// Write cache state to file for persistence
} else {
// First, watch files.
logger.info('Watch ' + options.pattern + '...');
const watcher = chokidar.watch([options.pattern.replace(/\\/g, '/')], { cwd });
logger.info(`Watch ${options.pattern}...`);
const watcher = chokidar.watch([options.pattern.replace(/\\/gu, '/')], { cwd });
watcher.on('all', (eventName, relativeFilePath) => {
const filePath = resolve(cwd, relativeFilePath);

Expand Down
5 changes: 3 additions & 2 deletions packages/happy-css-modules/src/test-util/jest/resolver.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
const nativeModule = require('node:module');

// workaround for https://github.com/facebook/jest/issues/12270#issuecomment-1194746382
Expand All @@ -14,15 +15,15 @@ const nativeModule = require('node:module');
* pathFilter?: (pkg: any, path: string, relativePath: string) => string;
* rootDir?: string;
* }} ResolverOptions
* */
*/

/** @type {(path: string, options: ResolverOptions) => string} */
function resolver(module, options) {
const { basedir, defaultResolver } = options;
try {
return defaultResolver(module, options);
// eslint-disable-next-line no-unused-vars
} catch (error) {
} catch (_error) {
return nativeModule.createRequire(basedir).resolve(module);
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/happy-css-modules/src/test-util/tsserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export async function createTSServer() {
const results: { identifier: string; definitions: Definition[] }[] = [];

for (let i = 0; i < identifiers.length; i++) {
// eslint-disable-next-line no-await-in-loop
const response: server.protocol.DefinitionResponse = await server.message({
seq: 0,
type: 'request',
Expand Down
4 changes: 3 additions & 1 deletion packages/happy-css-modules/src/test-util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ export function fakeToken(args: {
}

export async function waitForAsyncTask(ms?: number): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, ms ?? 0));
await new Promise((resolve) => {
setTimeout(resolve, ms ?? 0);
});
}

export async function exists(path: string): Promise<boolean> {
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-css-modules/src/transformer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { createScssTransformer } from './scss-transformer.js';
/**
* The value returned from the transformer.
* `false` means to skip transpiling on that file.
* */
*/
export type TransformResult =
| {
/** The transformed code. */
Expand Down
Loading
Loading