diff --git a/.husky/pre-commit b/.husky/pre-commit index 9cf36f8fa8f..be2b0e1d256 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash ensure_types_are_up_to_date() { types_path="packages/calcite-components/src/components.d.ts" @@ -9,6 +9,21 @@ ensure_types_are_up_to_date() { fi } +update_stylelint_config_if_sass_file_edited() { + staged_files="$(git diff --cached --name-only --diff-filter=ACM)" + + for file in $staged_files; do + if [[ "$file" == *.scss ]]; then + npm run util:update-stylelint-custom-sass-functions + break + fi + done + + if [ -n "$(git diff --name-only -- "packages/calcite-components/.stylelintrc.json")" ]; then + git add "packages/calcite-components/.stylelintrc.json" + fi +} + check_ui_icon_name_consistency() { svg_icon_dir="packages/calcite-ui-icons/icons" @@ -43,7 +58,8 @@ check_ui_icon_name_consistency() { } lint-staged -ensure_types_are_up_to_date check_ui_icon_name_consistency +ensure_types_are_up_to_date +update_stylelint_config_if_sass_file_edited exit 0 diff --git a/package.json b/package.json index a5bd22f4e9d..81058d45560 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "util:is-next-deployable": "tsx support/isNextDeployable.ts", "util:push-tags": "git push origin HEAD --follow-tags", "util:upload-release-assets": "tsx support/uploadReleaseAssets.ts", + "util:update-stylelint-custom-sass-functions": "tsx support/updateStylelintCustomSassFunctions.ts", "util:remove-prerelease-changelog-entries": "tsx support/removePrereleaseChangelogEntries.ts", "util:sync-linked-package-versions": "tsx support/syncLinkedPackageVersions.ts" }, diff --git a/packages/calcite-components/.stylelintrc.cjs b/packages/calcite-components/.stylelintrc.cjs index beb0fee57da..c2364bb51f9 100644 --- a/packages/calcite-components/.stylelintrc.cjs +++ b/packages/calcite-components/.stylelintrc.cjs @@ -1,5 +1,12 @@ // @ts-check +// ⚠️ AUTO-GENERATED CODE - DO NOT EDIT +const customFunctions = [ + "get-trailing-text-input-padding", + "scale-duration" +]; +// ⚠️ END OF AUTO-GENERATED CODE + const scssPatternRules = [ "scss/at-function-pattern", "scss/dollar-variable-pattern", @@ -50,7 +57,7 @@ const rules = { "scss/function-no-unknown": [ true, { - ignoreFunctions: ["get-trailing-text-input-padding", "scale-duration", "theme", "var"], + ignoreFunctions: [...customFunctions, "theme", "var"], severity: "error", }, ], diff --git a/support/updateStylelintCustomSassFunctions.ts b/support/updateStylelintCustomSassFunctions.ts new file mode 100644 index 00000000000..bd4b1cef104 --- /dev/null +++ b/support/updateStylelintCustomSassFunctions.ts @@ -0,0 +1,63 @@ +/** + * This script updates a variable from the stylelint config file with a list of custom Sass functions found in the project. + * This helps stylelint flag unknown functions that may be unintentionally used. + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +console.info('Scanning custom functions for Stylelint config update.'); + +const rootDirectory = path.join(__dirname, '..'); + +function collectSassFiles(dir: string): string[] { + const sassFiles: string[] = []; + + try { + fs.readdirSync(dir, { recursive: true, withFileTypes: true }).forEach(dirent => { + const fullPath = path.join(dir, dirent.name); + + if (dirent.isFile() && fullPath.endsWith('.scss')) { + sassFiles.push(fullPath); + } + }); + } catch (err) { + console.error(`Error reading directory: ${dir}`, err); + } + + return sassFiles; +} + +const customFunctionPattern = /@function\s+([a-zA-Z0-9_-]+)/g; +const customFunctions = new Set(); +const sassFiles = collectSassFiles(rootDirectory); + +sassFiles.forEach(filePath => { + try { + const content = fs.readFileSync(filePath, 'utf8'); + let match: RegExpExecArray | null; + + while ((match = customFunctionPattern.exec(content)) !== null) { + customFunctions.add(match[1]); + } + } catch (err) { + console.error(`Error reading file: ${filePath}`, err); + } +}); + +const stylelintConfigPath = path.join(__dirname, "..", "packages", "calcite-components", ".stylelintrc.cjs"); + +try { + const stylelintConfigContent = fs.readFileSync(stylelintConfigPath, 'utf8'); + const customFunctionsPattern = /const customFunctions = \[[\s\S]*?\];/; + + const updatedConfigContent = stylelintConfigContent.replace( + customFunctionsPattern, + `const customFunctions = ${JSON.stringify(Array.from(customFunctions).sort(), null, 2)};` + ); + + fs.writeFileSync(stylelintConfigPath, updatedConfigContent); + console.info('Stylelint configuration updated successfully'); +} catch (err) { + console.error(`Error updating Stylelint configuration: ${stylelintConfigPath}`, err); +}