From 6a38f3ff722ea2d611f071dc5ee358395c681017 Mon Sep 17 00:00:00 2001 From: Ido Rosenthal Date: Sun, 10 Jul 2022 17:47:49 +0300 Subject: [PATCH] Stylable v5.0.0 (#2411) --- .github/workflows/tests.yml | 2 +- package-lock.json | 469 ++++++++-------- package.json | 6 +- packages/build-tools/package.json | 6 +- .../src/process-url-dependencies.ts | 5 +- packages/cli/README.md | 2 +- packages/cli/package.json | 18 +- packages/cli/src/base-generator.ts | 54 +- packages/cli/src/build-single-file.ts | 32 +- packages/cli/src/build-stylable.ts | 15 +- packages/cli/src/build.ts | 101 ++-- packages/cli/src/code-mods/code-mods.ts | 2 +- ...t-global-custom-property-to-at-property.ts | 7 +- packages/cli/src/config/resolve-options.ts | 16 +- packages/cli/src/diagnostics-manager.ts | 8 +- packages/cli/src/index.ts | 2 +- packages/cli/src/report-diagnostics.ts | 12 +- packages/cli/src/stc-builder.ts | 7 +- packages/cli/src/types.ts | 2 +- packages/cli/src/watch-handler.ts | 3 +- packages/cli/test/build.spec.ts | 60 +- packages/cli/test/cli.spec.ts | 69 ++- ...bal-custom-property-to-at-property.spec.ts | 5 +- .../codemods/st-import-to-at-import.spec.ts | 2 +- packages/cli/test/config-options.spec.ts | 18 +- packages/cli/test/config-projects.spec.ts | 9 +- .../test/fixtures/named-exports-generator.ts | 8 +- packages/cli/test/fixtures/test-generator.ts | 4 +- packages/cli/test/generate-index.spec.ts | 143 ++++- packages/cli/test/tsconfig.json | 1 + .../cli/test/watch-multiple-projects.spec.ts | 12 +- .../cli/test/watch-single-project.spec.ts | 15 +- packages/code-formatter/package.json | 6 +- packages/core-test-kit/package.json | 6 +- packages/core-test-kit/src/diagnostics.ts | 55 +- .../core-test-kit/src/generate-test-util.ts | 25 +- packages/core-test-kit/src/index.ts | 1 + .../core-test-kit/src/inline-expectation.ts | 12 +- .../core-test-kit/src/matchers/results.ts | 16 +- .../core-test-kit/src/test-stylable-core.ts | 4 +- .../test/inline-expectations.spec.ts | 87 +-- .../test/test-stylable-core.spec.ts | 2 +- packages/core/package.json | 4 +- packages/core/src/custom-values.ts | 2 +- .../deprecated/deprecated-selector-utils.ts | 312 ----------- .../deprecated/deprecated-stylable-utils.ts | 225 -------- packages/core/src/deprecated/leftovers.ts | 8 - .../core/src/deprecated/memory-minimal-fs.ts | 104 ---- .../src/deprecated/postcss-ast-extension.ts | 55 +- packages/core/src/deprecated/value-mapping.ts | 45 -- packages/core/src/diagnostics.ts | 59 +- packages/core/src/features/css-class.ts | 100 ++-- .../core/src/features/css-custom-property.ts | 79 ++- packages/core/src/features/css-keyframes.ts | 64 ++- packages/core/src/features/css-layer.ts | 73 ++- packages/core/src/features/css-type.ts | 40 +- packages/core/src/features/diagnostics.ts | 18 +- packages/core/src/features/feature.ts | 5 + packages/core/src/features/index.ts | 6 +- .../core/src/features/st-custom-selector.ts | 132 +++++ packages/core/src/features/st-global.ts | 12 +- packages/core/src/features/st-import.ts | 118 ++-- packages/core/src/features/st-mixin.ts | 520 +++++++++++------- packages/core/src/features/st-scope.ts | 32 +- packages/core/src/features/st-symbol.ts | 41 +- packages/core/src/features/st-var.ts | 175 ++++-- packages/core/src/functions.ts | 45 +- .../core/src/helpers/css-custom-property.ts | 76 +-- packages/core/src/helpers/custom-selector.ts | 132 +++++ packages/core/src/helpers/custom-state.ts | 19 +- packages/core/src/helpers/global.ts | 2 +- packages/core/src/helpers/import.ts | 170 +++--- packages/core/src/helpers/mixin.ts | 21 +- packages/core/src/helpers/namespace.ts | 5 +- packages/core/src/helpers/rule.ts | 44 +- packages/core/src/helpers/value.ts | 30 +- packages/core/src/index-deprecated.ts | 367 ------------ packages/core/src/index-internal.ts | 50 +- packages/core/src/index.ts | 21 +- packages/core/src/parser.ts | 3 +- packages/core/src/pseudo-states.ts | 215 ++++---- packages/core/src/replace-rule-selector.ts | 77 --- packages/core/src/report-diagnostic.ts | 13 +- packages/core/src/state-validators.ts | 21 - packages/core/src/stylable-assets.ts | 33 +- packages/core/src/stylable-meta.ts | 91 +-- packages/core/src/stylable-processor.ts | 263 ++++----- packages/core/src/stylable-resolver.ts | 29 +- packages/core/src/stylable-transformer.ts | 131 +++-- packages/core/src/stylable-utils.ts | 69 +-- packages/core/src/stylable-value-parsers.ts | 8 +- packages/core/src/stylable.ts | 142 ++--- packages/core/src/types.ts | 36 +- .../core/src/visit-meta-css-dependencies.ts | 50 +- packages/core/test/arguement-parser.spec.ts | 6 +- .../core/test/cached-process-file.spec.ts | 2 +- packages/core/test/custom-selectors.spec.ts | 23 +- .../deprecated-selector-utils.spec.ts | 218 -------- .../deprecated-stylable-utils.spec.ts | 211 ------- packages/core/test/diagnostic-codes.spec.ts | 68 +++ packages/core/test/diagnostics.spec.ts | 41 +- .../core/test/extend-function-parser.spec.ts | 2 +- packages/core/test/features/css-class.spec.ts | 151 ++++- .../test/features/css-custom-property.spec.ts | 77 +-- .../core/test/features/css-keyframes.spec.ts | 54 +- packages/core/test/features/css-layer.spec.ts | 35 +- packages/core/test/features/css-type.spec.ts | 24 +- .../test/features/st-custom-selector.spec.ts | 44 +- packages/core/test/features/st-global.spec.ts | 10 +- packages/core/test/features/st-import.spec.ts | 169 ++++-- packages/core/test/features/st-mixin.spec.ts | 467 +++++++++------- packages/core/test/features/st-scope.spec.ts | 66 +++ packages/core/test/features/st-symbol.spec.ts | 46 +- packages/core/test/features/st-var.spec.ts | 98 ++-- packages/core/test/functions.spec.ts | 18 +- .../core/test/helpers/custom-selector.spec.ts | 252 +++++++++ packages/core/test/helpers/import.spec.ts | 24 +- packages/core/test/helpers/mixin.spec.ts | 6 +- packages/core/test/helpers/rule.spec.ts | 2 +- .../test/intellisense/intellisense.spec.ts | 5 +- packages/core/test/pseudo-states.spec.ts | 221 ++------ packages/core/test/scope-directive.spec.ts | 93 +--- packages/core/test/stylable-assets.spec.ts | 21 +- packages/core/test/stylable-processor.spec.ts | 25 +- packages/core/test/stylable-resolver.spec.ts | 27 +- .../test/stylable-transformer/global.spec.ts | 8 +- .../stylable-transformer/post-process.spec.ts | 4 +- .../test/stylable-transformer/scoping.spec.ts | 2 +- packages/core/test/stylable-utils.spec.ts | 19 +- packages/core/test/stylable.spec.ts | 84 ++- packages/create-stylable-app/package.json | 4 +- packages/custom-value/package.json | 6 +- packages/custom-value/src/st-border.ts | 4 +- packages/dom-test-kit/package.json | 8 +- .../dom-test-kit/src/stylable-dom-util.ts | 2 +- packages/e2e-test-kit/package.json | 6 +- packages/e2e-test-kit/src/dts-kit.ts | 2 +- packages/eslint-plugin-stylable/package.json | 4 +- .../src/stylable-es-lint.ts | 6 +- packages/experimental-loader/package.json | 8 +- .../experimental-loader/src/add-build-info.ts | 4 +- .../src/cached-stylable-factory.ts | 2 +- .../src/create-runtime-target-code.ts | 2 +- .../src/stylable-runtime-loader.ts | 2 +- .../src/stylable-transform-loader.ts | 14 +- .../test/errors-integration.spec.ts | 10 +- .../projects/errors-integration/index.st.css | 6 +- .../experimental-loader/test/tsconfig.json | 1 + packages/jest/package.json | 12 +- packages/language-service/package.json | 12 +- .../src/lib/completion-providers.ts | 31 +- .../language-service/src/lib/diagnosis.ts | 10 +- .../src/lib/feature/color-provider.ts | 8 +- .../src/lib/feature/pseudo-class.ts | 4 +- packages/language-service/src/lib/provider.ts | 67 +-- .../tag/state-def-with-param-tag-start.st.css | 3 - .../test/lib/completions/states.spec.ts | 24 - .../test/lib/diagnostics.spec.ts | 6 +- .../test/lib/signatures.spec.ts | 16 +- .../test/test-kit/asserters.ts | 5 +- .../test/test-kit/diagnostics-setup.ts | 2 +- .../test/test-kit/stylable-fixtures-lsp.ts | 2 +- .../test/test-kit/stylable-in-memory-lsp.ts | 2 +- packages/module-utils/package.json | 6 +- .../src/generate-dts-sourcemaps.ts | 8 +- packages/module-utils/src/generate-dts.ts | 17 +- packages/module-utils/src/module-factory.ts | 7 +- packages/module-utils/src/module-source.ts | 2 +- packages/node/package.json | 8 +- packages/node/test/require-hook.spec.ts | 2 +- packages/optimizer/package.json | 6 +- packages/optimizer/src/stylable-optimizer.ts | 46 +- .../optimizer/test/ast-optimizations.spec.ts | 3 - .../optimizer/test/stylable-optimizer.spec.ts | 11 +- packages/rollup-plugin/package.json | 16 +- packages/rollup-plugin/src/index.ts | 12 +- packages/rollup-plugin/src/plugin-utils.ts | 5 +- packages/runtime/package.json | 4 +- packages/schema-extract/package.json | 6 +- packages/schema-extract/src/cssdocs.ts | 2 +- packages/schema-extract/src/main.ts | 12 +- packages/schema-extract/test/test.spec.ts | 18 - packages/uni-driver/package.json | 4 +- packages/webpack-extensions/package.json | 10 +- .../src/create-metadata-stylesheet.ts | 4 +- .../src/stylable-forcestates-plugin.ts | 6 +- .../src/stylable-manifest-plugin.ts | 2 +- .../src/stylable-metadata-loader.ts | 2 +- .../unit/forcestates-plugin-plugins.spec.ts | 2 +- .../test/unit/forcestates-plugin.spec.ts | 20 +- packages/webpack-plugin/package.json | 18 +- packages/webpack-plugin/src/loader.ts | 8 +- packages/webpack-plugin/src/plugin-utils.ts | 2 +- packages/webpack-plugin/src/plugin.ts | 17 +- packages/webpack-plugin/src/types.ts | 3 +- .../test/e2e/errors-project.spec.ts | 10 +- .../test/e2e/namespace-generation-project.ts | 2 +- .../autoprefixer-project/webpack.config.js | 2 +- .../emit-info-diagnostic/webpack.config.js | 10 +- .../projects/errors-project/src/index.st.css | 4 +- .../hooked-project/stylable.config.js | 2 +- .../projects/optimizations/src/index.st.css | 12 +- pleb.config.mjs | 8 +- 203 files changed, 4447 insertions(+), 4531 deletions(-) delete mode 100644 packages/core/src/deprecated/deprecated-selector-utils.ts delete mode 100644 packages/core/src/deprecated/deprecated-stylable-utils.ts delete mode 100644 packages/core/src/deprecated/leftovers.ts delete mode 100644 packages/core/src/deprecated/memory-minimal-fs.ts delete mode 100644 packages/core/src/deprecated/value-mapping.ts create mode 100644 packages/core/src/features/st-custom-selector.ts create mode 100644 packages/core/src/helpers/custom-selector.ts delete mode 100644 packages/core/src/index-deprecated.ts delete mode 100644 packages/core/src/replace-rule-selector.ts delete mode 100644 packages/core/test/deprecated/deprecated-selector-utils.spec.ts delete mode 100644 packages/core/test/deprecated/deprecated-stylable-utils.spec.ts create mode 100644 packages/core/test/diagnostic-codes.spec.ts create mode 100644 packages/core/test/features/st-scope.spec.ts create mode 100644 packages/core/test/helpers/custom-selector.spec.ts delete mode 100644 packages/language-service/test/fixtures/server-cases/states/with-param/tag/state-def-with-param-tag-start.st.css diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 13489e936..1ba0a2385 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [16, 14, 12] + node-version: [18, 16, 14] os: [ubuntu-latest, windows-latest, macOS-latest] steps: - uses: actions/checkout@v3 diff --git a/package-lock.json b/package-lock.json index 8350b5f70..66426e137 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "@types/memory-fs": "^0.3.3", "@types/mime": "^2.0.3", "@types/mocha": "^9.1.1", - "@types/node": "12", + "@types/node": "14", "@types/postcss-safe-parser": "^5.0.1", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", @@ -70,7 +70,7 @@ "rimraf": "^3.0.2", "rollup": "^2.76.0", "source-map": "^0.7.4", - "source-map-loader": "^3.0.1", + "source-map-loader": "^4.0.0", "style-loader": "^3.3.1", "ts-expect": "^1.3.0", "typescript": "~4.7.4", @@ -79,7 +79,7 @@ "yargs": "^17.5.1" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "node_modules/@eslint/eslintrc": { @@ -664,9 +664,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" + "version": "14.18.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.21.tgz", + "integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==" }, "node_modules/@types/parse5": { "version": "6.0.3", @@ -1124,23 +1124,31 @@ } }, "node_modules/@wixc3/resolve-directory-context": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@wixc3/resolve-directory-context/-/resolve-directory-context-2.0.0.tgz", - "integrity": "sha512-pSmI73kLoXQz2j/8sLCunUqaUcjQe46isXW64p1AaCv7XlBWs84ZTyVEhcBAsDvSMWw8FeU2HFSmdq7bf0nXCQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@wixc3/resolve-directory-context/-/resolve-directory-context-3.0.0.tgz", + "integrity": "sha512-CmA26NRG2FQDbjwAPp3jMr1FHmGgc9CJDtz2WwMMXzJcv8xKuRiTa0mtnVtdXcvWGaHZks/wG4EfZ3s1bcXzhw==", "dependencies": { - "minimatch": "^4.1.1", - "type-fest": "^2.11.2" + "minimatch": "^5.0.1", + "type-fest": "^2.12.2" }, "engines": { - "node": ">=12" + "node": ">=14" + } + }, + "node_modules/@wixc3/resolve-directory-context/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" } }, "node_modules/@wixc3/resolve-directory-context/node_modules/minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=10" @@ -3194,19 +3202,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -6049,24 +6044,24 @@ } }, "node_modules/source-map-loader": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.1.tgz", - "integrity": "sha512-Vp1UsfyPvgujKQzi4pyDiTOnE3E4H+yHvkVRN3c/9PJmQS4CQJExvcDvaX/D+RV+xQben9HJ56jMJS3CgUeWyA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", "dev": true, "dependencies": { - "abab": "^2.0.5", + "abab": "^2.0.6", "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.1" + "source-map-js": "^1.0.2" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 14.15.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "webpack": "^5.72.1" } }, "node_modules/source-map-loader/node_modules/iconv-lite": { @@ -6656,49 +6651,44 @@ "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==" }, "node_modules/vscode-css-languageservice": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-5.4.2.tgz", - "integrity": "sha512-DT7+7vfdT2HDNjDoXWtYJ0lVDdeDEdbMNdK4PKqUl2MS8g7PWt7J5G9B6k9lYox8nOfhCEjLnoNC3UKHHCR1lg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.0.1.tgz", + "integrity": "sha512-81n/eeYuJwQdvpoy6IK1258PtPbO720fl13FcJ5YQECPyHMFkmld1qKHwPJkyLbLPfboqJPM53ys4xW8v+iBVw==", "dependencies": { "vscode-languageserver-textdocument": "^1.0.4", - "vscode-languageserver-types": "^3.16.0", - "vscode-nls": "^5.0.0", + "vscode-languageserver-types": "^3.17.1", + "vscode-nls": "^5.0.1", "vscode-uri": "^3.0.3" } }, "node_modules/vscode-jsonrpc": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", - "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz", + "integrity": "sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ==", "engines": { - "node": ">=8.0.0 || >=10.0.0" + "node": ">=14.0.0" } }, "node_modules/vscode-languageserver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", - "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.0.1.tgz", + "integrity": "sha512-sn7SjBwWm3OlmLtgg7jbM0wBULppyL60rj8K5HF0ny/MzN+GzPBX1kCvYdybhl7UW63V5V5tRVnyB8iwC73lSQ==", "dependencies": { - "vscode-languageserver-protocol": "3.16.0" + "vscode-languageserver-protocol": "3.17.1" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", - "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz", + "integrity": "sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg==", "dependencies": { - "vscode-jsonrpc": "6.0.0", - "vscode-languageserver-types": "3.16.0" + "vscode-jsonrpc": "8.0.1", + "vscode-languageserver-types": "3.17.1" } }, - "node_modules/vscode-languageserver-protocol/node_modules/vscode-languageserver-types": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", - "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" - }, "node_modules/vscode-languageserver-textdocument": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.5.tgz", @@ -7119,30 +7109,30 @@ }, "packages/build-tools": { "name": "@stylable/build-tools", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "find-config": "^1.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/cli": { "name": "@stylable/cli", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { "@file-services/node": "^6.0.0", "@file-services/types": "^6.0.0", - "@stylable/build-tools": "^4.14.0", - "@stylable/code-formatter": "^4.14.0", - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@wixc3/resolve-directory-context": "^2.0.0", + "@stylable/build-tools": "^5.0.0", + "@stylable/code-formatter": "^5.0.0", + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@wixc3/resolve-directory-context": "^3.0.0", "decache": "^4.6.1", "lodash.camelcase": "^4.3.0", "lodash.upperfirst": "^4.3.1", @@ -7154,24 +7144,24 @@ "stc-format": "bin/stc-format.js" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/code-formatter": { "name": "@stylable/code-formatter", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "js-beautify": "^1.14.4" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/core": { "name": "@stylable/core", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { "@tokey/css-selector-parser": "^0.6.0", @@ -7191,22 +7181,22 @@ "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/core-test-kit": { "name": "@stylable/core-test-kit", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { "@file-services/memory": "^6.0.0", - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "chai": "^4.3.6", "flat": "^5.0.2", "postcss": "^8.4.14" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/core/node_modules/balanced-match": { @@ -7215,7 +7205,7 @@ "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==" }, "packages/create-stylable-app": { - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { "validate-npm-package-name": "^4.0.0", @@ -7225,67 +7215,67 @@ "create-stylable-app": "bin/create-stylable-app.js" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/custom-value": { "name": "@stylable/custom-value", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0" + "@stylable/core": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/dom-test-kit": { "name": "@stylable/dom-test-kit", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/runtime": "^5.0.0", "@tokey/css-selector-parser": "^0.6.0" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/e2e-test-kit": { "name": "@stylable/e2e-test-kit", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/runtime": "^4.14.0", + "@stylable/runtime": "^5.0.0", "express": "^4.18.1", "node-eval": "^2.0.0", "playwright-core": "^1.23.2", "rimraf": "^3.0.2" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "peerDependencies": { "webpack": "^5.30.0" } }, "packages/eslint-plugin-stylable": { - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "@typescript-eslint/experimental-utils": "^5.30.5" } }, "packages/experimental-loader": { "name": "@stylable/experimental-loader", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@stylable/runtime": "^5.0.0", "css-loader": "^6.7.1", "decache": "^4.6.1", "loader-utils": "^3.2.0" @@ -7301,37 +7291,37 @@ }, "packages/jest": { "name": "@stylable/jest", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/runtime": "^4.14.0" + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/runtime": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/language-service": { "name": "@stylable/language-service", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { "@file-services/types": "^6.0.0", "@file-services/typescript": "^6.0.0", - "@stylable/code-formatter": "^4.14.0", - "@stylable/core": "^4.14.0", + "@stylable/code-formatter": "^5.0.0", + "@stylable/core": "^5.0.0", "css-selector-tokenizer": "^0.8.0", "postcss": "^8.4.14", "postcss-value-parser": "^4.2.0", - "vscode-css-languageservice": "^5.4.2", - "vscode-languageserver": "^7.0.0", + "vscode-css-languageservice": "^6.0.1", + "vscode-languageserver": "^8.0.1", "vscode-languageserver-textdocument": "^1.0.5", "vscode-uri": "^3.0.3" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "peerDependencies": { "typescript": ">=3.8" @@ -7339,60 +7329,60 @@ }, "packages/module-utils": { "name": "@stylable/module-utils", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "@tokey/core": "^1.3.0", "vlq": "^2.0.4" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/node": { "name": "@stylable/node", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", "find-config": "^1.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/optimizer": { "name": "@stylable/optimizer", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "@tokey/css-selector-parser": "^0.6.0", "csso": "^5.0.3", "postcss": "^8.4.14" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/rollup-plugin": { "name": "@stylable/rollup-plugin", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/build-tools": "^4.14.0", - "@stylable/cli": "^4.14.0", - "@stylable/core": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/build-tools": "^5.0.0", + "@stylable/cli": "^5.0.0", + "@stylable/core": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@stylable/runtime": "^5.0.0", "decache": "^4.6.1", "mime": "^3.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "peerDependencies": { "rollup": "^2.70.0" @@ -7411,45 +7401,45 @@ }, "packages/runtime": { "name": "@stylable/runtime", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/schema-extract": { "name": "@stylable/schema-extract", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "jest-docblock": "^28.1.1" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/uni-driver": { "name": "@stylable/uni-driver", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14.14.0" } }, "packages/webpack-extensions": { "name": "@stylable/webpack-extensions", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/core": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/webpack-plugin": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/webpack-plugin": "^5.0.0", "@tokey/css-selector-parser": "^0.6.0", "lodash.clonedeep": "^4.5.0" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "peerDependencies": { "webpack": "^5.30.0" @@ -7457,23 +7447,23 @@ }, "packages/webpack-plugin": { "name": "@stylable/webpack-plugin", - "version": "4.14.0", + "version": "5.0.0", "license": "MIT", "dependencies": { - "@stylable/build-tools": "^4.14.0", - "@stylable/cli": "^4.14.0", - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/build-tools": "^5.0.0", + "@stylable/cli": "^5.0.0", + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@stylable/runtime": "^5.0.0", "decache": "^4.6.1", "find-config": "^1.0.0", "lodash.clonedeep": "^4.5.0", "postcss": "^8.4.14" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "peerDependencies": { "webpack": "^5.30.0" @@ -7661,7 +7651,7 @@ "@stylable/build-tools": { "version": "file:packages/build-tools", "requires": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "find-config": "^1.0.0" } }, @@ -7670,13 +7660,13 @@ "requires": { "@file-services/node": "^6.0.0", "@file-services/types": "^6.0.0", - "@stylable/build-tools": "^4.14.0", - "@stylable/code-formatter": "^4.14.0", - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@wixc3/resolve-directory-context": "^2.0.0", + "@stylable/build-tools": "^5.0.0", + "@stylable/code-formatter": "^5.0.0", + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@wixc3/resolve-directory-context": "^3.0.0", "decache": "^4.6.1", "lodash.camelcase": "^4.3.0", "lodash.upperfirst": "^4.3.1", @@ -7686,7 +7676,7 @@ "@stylable/code-formatter": { "version": "file:packages/code-formatter", "requires": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "js-beautify": "^1.14.4" } }, @@ -7721,7 +7711,7 @@ "version": "file:packages/core-test-kit", "requires": { "@file-services/memory": "^6.0.0", - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "chai": "^4.3.6", "flat": "^5.0.2", "postcss": "^8.4.14" @@ -7730,21 +7720,21 @@ "@stylable/custom-value": { "version": "file:packages/custom-value", "requires": { - "@stylable/core": "^4.14.0" + "@stylable/core": "^5.0.0" } }, "@stylable/dom-test-kit": { "version": "file:packages/dom-test-kit", "requires": { - "@stylable/core": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/runtime": "^5.0.0", "@tokey/css-selector-parser": "^0.6.0" } }, "@stylable/e2e-test-kit": { "version": "file:packages/e2e-test-kit", "requires": { - "@stylable/runtime": "^4.14.0", + "@stylable/runtime": "^5.0.0", "express": "^4.18.1", "node-eval": "^2.0.0", "playwright-core": "^1.23.2", @@ -7754,9 +7744,9 @@ "@stylable/experimental-loader": { "version": "file:packages/experimental-loader", "requires": { - "@stylable/core": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@stylable/runtime": "^5.0.0", "css-loader": "^6.7.1", "decache": "^4.6.1", "loader-utils": "^3.2.0" @@ -7772,10 +7762,10 @@ "@stylable/jest": { "version": "file:packages/jest", "requires": { - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/runtime": "^4.14.0" + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/runtime": "^5.0.0" } }, "@stylable/language-service": { @@ -7783,13 +7773,13 @@ "requires": { "@file-services/types": "^6.0.0", "@file-services/typescript": "^6.0.0", - "@stylable/code-formatter": "^4.14.0", - "@stylable/core": "^4.14.0", + "@stylable/code-formatter": "^5.0.0", + "@stylable/core": "^5.0.0", "css-selector-tokenizer": "^0.8.0", "postcss": "^8.4.14", "postcss-value-parser": "^4.2.0", - "vscode-css-languageservice": "^5.4.2", - "vscode-languageserver": "^7.0.0", + "vscode-css-languageservice": "^6.0.1", + "vscode-languageserver": "^8.0.1", "vscode-languageserver-textdocument": "^1.0.5", "vscode-uri": "^3.0.3" } @@ -7797,7 +7787,7 @@ "@stylable/module-utils": { "version": "file:packages/module-utils", "requires": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "@tokey/core": "^1.3.0", "vlq": "^2.0.4" } @@ -7805,15 +7795,15 @@ "@stylable/node": { "version": "file:packages/node", "requires": { - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", "find-config": "^1.0.0" } }, "@stylable/optimizer": { "version": "file:packages/optimizer", "requires": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "@tokey/css-selector-parser": "^0.6.0", "csso": "^5.0.3", "postcss": "^8.4.14" @@ -7822,12 +7812,12 @@ "@stylable/rollup-plugin": { "version": "file:packages/rollup-plugin", "requires": { - "@stylable/build-tools": "^4.14.0", - "@stylable/cli": "^4.14.0", - "@stylable/core": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/build-tools": "^5.0.0", + "@stylable/cli": "^5.0.0", + "@stylable/core": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@stylable/runtime": "^5.0.0", "decache": "^4.6.1", "mime": "^3.0.0" }, @@ -7845,7 +7835,7 @@ "@stylable/schema-extract": { "version": "file:packages/schema-extract", "requires": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "jest-docblock": "^28.1.1" } }, @@ -7855,9 +7845,9 @@ "@stylable/webpack-extensions": { "version": "file:packages/webpack-extensions", "requires": { - "@stylable/core": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/webpack-plugin": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/webpack-plugin": "^5.0.0", "@tokey/css-selector-parser": "^0.6.0", "lodash.clonedeep": "^4.5.0" } @@ -7865,13 +7855,13 @@ "@stylable/webpack-plugin": { "version": "file:packages/webpack-plugin", "requires": { - "@stylable/build-tools": "^4.14.0", - "@stylable/cli": "^4.14.0", - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/build-tools": "^5.0.0", + "@stylable/cli": "^5.0.0", + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@stylable/runtime": "^5.0.0", "decache": "^4.6.1", "find-config": "^1.0.0", "lodash.clonedeep": "^4.5.0", @@ -8153,9 +8143,9 @@ "dev": true }, "@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" + "version": "14.18.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.21.tgz", + "integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==" }, "@types/parse5": { "version": "6.0.3", @@ -8516,20 +8506,28 @@ } }, "@wixc3/resolve-directory-context": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@wixc3/resolve-directory-context/-/resolve-directory-context-2.0.0.tgz", - "integrity": "sha512-pSmI73kLoXQz2j/8sLCunUqaUcjQe46isXW64p1AaCv7XlBWs84ZTyVEhcBAsDvSMWw8FeU2HFSmdq7bf0nXCQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@wixc3/resolve-directory-context/-/resolve-directory-context-3.0.0.tgz", + "integrity": "sha512-CmA26NRG2FQDbjwAPp3jMr1FHmGgc9CJDtz2WwMMXzJcv8xKuRiTa0mtnVtdXcvWGaHZks/wG4EfZ3s1bcXzhw==", "requires": { - "minimatch": "^4.1.1", - "type-fest": "^2.11.2" + "minimatch": "^5.0.1", + "type-fest": "^2.12.2" }, "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, "minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" } }, "type-fest": { @@ -9738,7 +9736,7 @@ "eslint-plugin-stylable": { "version": "file:packages/eslint-plugin-stylable", "requires": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "@typescript-eslint/experimental-utils": "^5.30.5" } }, @@ -10069,12 +10067,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -12090,14 +12082,14 @@ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" }, "source-map-loader": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.1.tgz", - "integrity": "sha512-Vp1UsfyPvgujKQzi4pyDiTOnE3E4H+yHvkVRN3c/9PJmQS4CQJExvcDvaX/D+RV+xQben9HJ56jMJS3CgUeWyA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", "dev": true, "requires": { - "abab": "^2.0.5", + "abab": "^2.0.6", "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.1" + "source-map-js": "^1.0.2" }, "dependencies": { "iconv-lite": { @@ -12515,43 +12507,36 @@ "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==" }, "vscode-css-languageservice": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-5.4.2.tgz", - "integrity": "sha512-DT7+7vfdT2HDNjDoXWtYJ0lVDdeDEdbMNdK4PKqUl2MS8g7PWt7J5G9B6k9lYox8nOfhCEjLnoNC3UKHHCR1lg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.0.1.tgz", + "integrity": "sha512-81n/eeYuJwQdvpoy6IK1258PtPbO720fl13FcJ5YQECPyHMFkmld1qKHwPJkyLbLPfboqJPM53ys4xW8v+iBVw==", "requires": { "vscode-languageserver-textdocument": "^1.0.4", - "vscode-languageserver-types": "^3.16.0", - "vscode-nls": "^5.0.0", + "vscode-languageserver-types": "^3.17.1", + "vscode-nls": "^5.0.1", "vscode-uri": "^3.0.3" } }, "vscode-jsonrpc": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", - "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==" + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz", + "integrity": "sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ==" }, "vscode-languageserver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", - "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.0.1.tgz", + "integrity": "sha512-sn7SjBwWm3OlmLtgg7jbM0wBULppyL60rj8K5HF0ny/MzN+GzPBX1kCvYdybhl7UW63V5V5tRVnyB8iwC73lSQ==", "requires": { - "vscode-languageserver-protocol": "3.16.0" + "vscode-languageserver-protocol": "3.17.1" } }, "vscode-languageserver-protocol": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", - "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz", + "integrity": "sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg==", "requires": { - "vscode-jsonrpc": "6.0.0", - "vscode-languageserver-types": "3.16.0" - }, - "dependencies": { - "vscode-languageserver-types": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", - "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" - } + "vscode-jsonrpc": "8.0.1", + "vscode-languageserver-types": "3.17.1" } }, "vscode-languageserver-textdocument": { diff --git a/package.json b/package.json index 10804adb6..4c00bdcf6 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@types/memory-fs": "^0.3.3", "@types/mime": "^2.0.3", "@types/mocha": "^9.1.1", - "@types/node": "12", + "@types/node": "14", "@types/postcss-safe-parser": "^5.0.1", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", @@ -76,7 +76,7 @@ "rimraf": "^3.0.2", "rollup": "^2.76.0", "source-map": "^0.7.4", - "source-map-loader": "^3.0.1", + "source-map-loader": "^4.0.0", "style-loader": "^3.3.1", "ts-expect": "^1.3.0", "typescript": "~4.7.4", @@ -85,7 +85,7 @@ "yargs": "^17.5.1" }, "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "repository": "git@github.com:wix/stylable.git", "author": "Wix.com", diff --git a/packages/build-tools/package.json b/packages/build-tools/package.json index e54ecc0c1..5a41c2bbc 100644 --- a/packages/build-tools/package.json +++ b/packages/build-tools/package.json @@ -1,13 +1,13 @@ { "name": "@stylable/build-tools", - "version": "4.14.0", + "version": "5.0.0", "description": "Collection of helper functions for Stylable based tooling.", "main": "dist/index.js", "scripts": { "test": "mocha \"./dist/test/**/*.spec.js\" --timeout 20000" }, "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "find-config": "^1.0.0" }, "files": [ @@ -17,7 +17,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/build-tools/src/process-url-dependencies.ts b/packages/build-tools/src/process-url-dependencies.ts index de5effee8..59a82ae6a 100644 --- a/packages/build-tools/src/process-url-dependencies.ts +++ b/packages/build-tools/src/process-url-dependencies.ts @@ -1,5 +1,6 @@ import type { UrlNode } from 'css-selector-tokenizer'; -import { isAsset, makeAbsolute, StylableMeta } from '@stylable/core'; +import type { StylableMeta } from '@stylable/core'; +import { isAsset, makeAbsolute } from '@stylable/core/dist/index-internal'; import { processDeclarationFunctions } from '@stylable/core/dist/process-declaration-functions'; import { dirname } from 'path'; @@ -25,7 +26,7 @@ export function processUrlDependencies( } }; - meta.outputAst!.walkDecls((node) => { + meta.targetAst!.walkDecls((node) => { processDeclarationFunctions( node, (functionNode) => { diff --git a/packages/cli/README.md b/packages/cli/README.md index 6c718649d..e6fa3a646 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -85,7 +85,7 @@ export interface BuildOptions { outDir: string; /** should the build need to output manifest file */ manifest?: string; - /** opt into build index file and specify the filepath for the generated index file */ + /** generates Stylable index file for the given name, the index file will reference Stylable sources from the `srcDir` unless the `outputSources` option is `true` in which case it will reference the `outDir` */ indexFile?: string; /** custom cli index generator class */ IndexGenerator?: typeof IndexGenerator; diff --git a/packages/cli/package.json b/packages/cli/package.json index b44fa2b5a..20dceb78d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@stylable/cli", - "version": "4.14.0", + "version": "5.0.0", "description": "A low-level utility used for working with Stylable projects", "main": "dist/index.js", "bin": { @@ -14,13 +14,13 @@ "dependencies": { "@file-services/node": "^6.0.0", "@file-services/types": "^6.0.0", - "@stylable/build-tools": "^4.14.0", - "@stylable/code-formatter": "^4.14.0", - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@wixc3/resolve-directory-context": "^2.0.0", + "@stylable/build-tools": "^5.0.0", + "@stylable/code-formatter": "^5.0.0", + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@wixc3/resolve-directory-context": "^3.0.0", "decache": "^4.6.1", "lodash.camelcase": "^4.3.0", "lodash.upperfirst": "^4.3.1", @@ -34,7 +34,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/cli/src/base-generator.ts b/packages/cli/src/base-generator.ts index a1b95b4ce..a930b0696 100644 --- a/packages/cli/src/base-generator.ts +++ b/packages/cli/src/base-generator.ts @@ -1,11 +1,11 @@ +import { nodeFs } from '@file-services/node'; import type { IFileSystem } from '@file-services/types'; import type { Stylable } from '@stylable/core'; import { STSymbol } from '@stylable/core/dist/features'; import camelcase from 'lodash.camelcase'; import upperfirst from 'lodash.upperfirst'; -import { basename, relative } from 'path'; import { normalizeRelative, ensureDirectory, tryRun } from './build-tools'; -import type { Log } from './logger'; +import { createDefaultLogger, Log } from './logger'; export interface ReExports { root: string; @@ -15,11 +15,29 @@ export interface ReExports { stVars: Record; } +export interface IndexGeneratorParameters { + indexFileTargetPath: string; + stylable: Stylable; + log?: Log; + fs?: Pick; +} + export class IndexGenerator { private indexFileOutput = new Map(); private collisionDetector = new NameCollisionDetector(); + private log: Log; + + public indexFileTargetPath: string; + public stylable: Stylable; + public fs: NonNullable; - constructor(public stylable: Stylable, private log: Log) {} + constructor({ log, stylable, indexFileTargetPath, fs }: IndexGeneratorParameters) { + this.stylable = stylable; + this.indexFileTargetPath = indexFileTargetPath; + + this.log = log ?? createDefaultLogger(); + this.fs = fs ?? nodeFs; + } public generateReExports(filePath: string): ReExports | undefined { return { @@ -31,38 +49,46 @@ export class IndexGenerator { }; } - public generateFileIndexEntry(filePath: string, fullOutDir: string) { + public generateFileIndexEntry(filePath: string) { const reExports = this.generateReExports(filePath); if (reExports) { this.checkForCollisions(reExports, filePath); this.log('[Generator Index]', `Add file: ${filePath}`); - this.indexFileOutput.set(normalizeRelative(relative(fullOutDir, filePath)), reExports); + this.indexFileOutput.set( + normalizeRelative( + this.fs.relative(this.fs.dirname(this.indexFileTargetPath), filePath) + ), + reExports + ); } } - public removeEntryFromIndex(filePath: string, fullOutDir: string) { - this.indexFileOutput.delete(normalizeRelative(relative(fullOutDir, filePath))); + public removeEntryFromIndex(filePath: string) { + this.indexFileOutput.delete( + normalizeRelative(this.fs.relative(this.fs.dirname(this.indexFileTargetPath), filePath)) + ); } - public async generateIndexFile(fs: IFileSystem, indexFileTargetPath: string) { - const indexFileContent = this.generateIndexSource(indexFileTargetPath); - ensureDirectory(fs.dirname(indexFileTargetPath), fs); + public async generateIndexFile(fs: IFileSystem) { + const indexFileContent = this.generateIndexSource(); + ensureDirectory(fs.dirname(this.indexFileTargetPath), fs); await tryRun( - () => fs.promises.writeFile(indexFileTargetPath, '\n' + indexFileContent + '\n'), + () => fs.promises.writeFile(this.indexFileTargetPath, '\n' + indexFileContent + '\n'), 'Write Index File Error' ); - this.log('[Generator Index]', 'creating index file: ' + indexFileTargetPath); + this.log('[Generator Index]', 'creating index file: ' + this.indexFileTargetPath); } public filename2varname(filePath: string) { - const varname = basename(basename(filePath, '.css'), '.st') // remove prefixes and .st.css ext + const varname = this.fs + .basename(this.fs.basename(filePath, '.css'), '.st') // remove prefixes and .st.css ext .replace(/^\d+/, ''); // remove leading numbers return upperfirst(camelcase(varname)); } - protected generateIndexSource(_indexFileTargetPath: string) { + protected generateIndexSource() { return [...this.indexFileOutput.entries()] .map(([from, reExports]) => createImportForComponent(from, reExports)) .join('\n'); diff --git a/packages/cli/src/build-single-file.ts b/packages/cli/src/build-single-file.ts index 259d73544..99526eaad 100644 --- a/packages/cli/src/build-single-file.ts +++ b/packages/cli/src/build-single-file.ts @@ -1,4 +1,5 @@ -import { isAsset, Stylable, StylableResults } from '@stylable/core'; +import type { Stylable, StylableResults } from '@stylable/core'; +import { isAsset } from '@stylable/core/dist/index-internal'; import { createModuleSource, generateDTSContent, @@ -9,15 +10,16 @@ import { ensureDirectory, tryRun } from './build-tools'; import { nameTemplate } from './name-template'; import type { Log } from './logger'; import { DiagnosticsManager, DiagnosticsMode } from './diagnostics-manager'; -import type { Diagnostic } from './report-diagnostics'; +import type { CLIDiagnostic } from './report-diagnostics'; import { errorMessages } from './messages'; +import type { IFileSystem } from '@file-services/types'; export interface BuildCommonOptions { fullOutDir: string; filePath: string; fullSrcDir: string; log: Log; - fs: any; + fs: IFileSystem; moduleFormats: string[]; outputCSS?: boolean; outputCSSNameTemplate?: string; @@ -85,7 +87,7 @@ export function buildSingleFile({ `Read File Error: ${filePath}` ); const res = tryRun( - () => stylable.transform(content, filePath), + () => stylable.transform(stylable.analyze(filePath)), errorMessages.STYLABLE_PROCESS(filePath) ); const optimizer = new StylableOptimizer(); @@ -154,7 +156,7 @@ export function buildSingleFile({ }); // .css if (outputCSS) { - let cssCode = res.meta.outputAst!.toString(); + let cssCode = res.meta.targetAst!.toString(); if (minify) { cssCode = optimizer.minifyCSS(cssCode); } @@ -211,6 +213,10 @@ export function buildSingleFile({ projectAssets.add(resolve(fileDirectory, url)); } } + + return { + targetFilePath, + }; } export function removeBuildProducts({ @@ -275,17 +281,23 @@ export function removeBuildProducts({ } log(mode, `removed: [${outputLogs.join(', ')}]`); + + return { + targetFilePath, + }; } -export function getAllDiagnostics(res: StylableResults): Diagnostic[] { +export function getAllDiagnostics(res: StylableResults): CLIDiagnostic[] { const diagnostics = res.meta.transformDiagnostics ? res.meta.diagnostics.reports.concat(res.meta.transformDiagnostics.reports) : res.meta.diagnostics.reports; - return diagnostics.map(({ message, node, options, type }) => { - const err = node.error(message, options); - const diagnostic: Diagnostic = { - type, + return diagnostics.map(({ message, node, word, severity, code }) => { + const err = node.error(message, { word }); + const diagnostic: CLIDiagnostic = { + severity, + node, + code, message: `${message}\n${err.showSourceCode(true)}`, ...(node.source?.start && {}), }; diff --git a/packages/cli/src/build-stylable.ts b/packages/cli/src/build-stylable.ts index 7ae3079e8..f3be6a0dc 100644 --- a/packages/cli/src/build-stylable.ts +++ b/packages/cli/src/build-stylable.ts @@ -1,14 +1,16 @@ import { nodeFs as fs } from '@file-services/node'; -import { Stylable, StylableConfig, StylableResolverCache } from '@stylable/core'; +import { Stylable, StylableConfig } from '@stylable/core'; +import type { StylableResolverCache } from '@stylable/core/dist/index-internal'; import { build } from './build'; import { projectsConfig } from './config/projects-config'; import { createBuildIdentifier, createDefaultOptions, + hasStylableCSSOutput, NAMESPACE_RESOLVER_MODULE_REQUEST, } from './config/resolve-options'; import { DiagnosticsManager } from './diagnostics-manager'; -import { createDefaultLogger } from './logger'; +import { createDefaultLogger, levels } from './logger'; import type { BuildContext, BuildOptions } from './types'; import { WatchHandler } from './watch-handler'; @@ -81,7 +83,14 @@ export async function buildStylable( log('[Project]', projectRoot, buildOptions); - const stylable = Stylable.create({ + if (!hasStylableCSSOutput(buildOptions)) { + log( + `No target output declared for "${identifier}", please provide one or more of the following target options: "cjs", "esm", "css", "stcss" or "indexFile"`, + levels.info + ); + } + + const stylable = new Stylable({ fileSystem, requireModule, projectRoot, diff --git a/packages/cli/src/build.ts b/packages/cli/src/build.ts index 474665d49..45988a67b 100644 --- a/packages/cli/src/build.ts +++ b/packages/cli/src/build.ts @@ -6,9 +6,10 @@ import { handleAssets } from './handle-assets'; import { buildSingleFile, removeBuildProducts } from './build-single-file'; import { DirectoryProcessService } from './directory-process-service/directory-process-service'; import { DiagnosticsManager } from './diagnostics-manager'; -import type { Diagnostic } from './report-diagnostics'; +import type { CLIDiagnostic } from './report-diagnostics'; import { tryRun } from './build-tools'; import { errorMessages, buildMessages } from './messages'; +import postcss from 'postcss'; export async function build( { @@ -57,7 +58,9 @@ export async function build( } const mode = watch ? '[Watch]' : '[Build]'; - const generator = new IndexGenerator(stylable, log); + const indexFileGenerator = indexFile + ? new IndexGenerator({ stylable, log, indexFileTargetPath: join(fullOutDir, indexFile) }) + : null; const buildGeneratedFiles = new Set(); const sourceFiles = new Set(); const assets = new Set(); @@ -118,8 +121,7 @@ export async function build( } diagnosticsManager.delete(identifier, deletedFile); sourceFiles.delete(deletedFile); - generator.removeEntryFromIndex(deletedFile, fullOutDir); - removeBuildProducts({ + const { targetFilePath } = removeBuildProducts({ fullOutDir, fullSrcDir, filePath: deletedFile, @@ -133,6 +135,12 @@ export async function build( dts, dtsSourceMap, }); + + if (indexFileGenerator) { + indexFileGenerator.removeEntryFromIndex( + outputSources ? targetFilePath : deletedFile + ); + } } } } @@ -150,13 +158,11 @@ export async function build( } for (const filePath of affectedFiles) { - if (!indexFile) { - // map st output file path to src file path - outputFiles.set( - join(fullOutDir, relative(fullSrcDir, filePath)), - new Set([filePath]) - ); - } + // map st output file path to src file path + outputFiles.set( + join(fullOutDir, relative(fullSrcDir, filePath)), + new Set([filePath]) + ); // remove assets from the affected files (handled in buildAggregatedEntities) if (assets.has(filePath)) { @@ -207,33 +213,35 @@ export async function build( function buildFiles(filesToBuild: Set, generated: Set) { for (const filePath of filesToBuild) { try { - if (indexFile) { - generator.generateFileIndexEntry(filePath, fullOutDir); - } else { - buildSingleFile({ - fullOutDir, - filePath, - fullSrcDir, - log, - fs, - stylable, - diagnosticsManager, - diagnosticsMode, - identifier, - projectAssets: assets, - moduleFormats, - includeCSSInJS, - outputCSS, - outputCSSNameTemplate, - outputSources, - useNamespaceReference, - injectCSSRequest, - optimize, - dts, - dtsSourceMap, - minify, - generated, - }); + const { targetFilePath } = buildSingleFile({ + fullOutDir, + filePath, + fullSrcDir, + log, + fs, + stylable, + diagnosticsManager, + diagnosticsMode, + identifier, + projectAssets: assets, + moduleFormats, + includeCSSInJS, + outputCSS, + outputCSSNameTemplate, + outputSources, + useNamespaceReference, + injectCSSRequest, + optimize, + dts, + dtsSourceMap, + minify, + generated, + }); + + if (indexFileGenerator) { + indexFileGenerator.generateFileIndexEntry( + outputSources ? targetFilePath : filePath + ); } } catch (error) { setFileErrorDiagnostic(filePath, error); @@ -249,6 +257,7 @@ export async function build( () => stylable.analyze(filePath), errorMessages.STYLABLE_PROCESS(filePath) ); + // todo: consider merging this API with stylable.getDependencies() for (const depFilePath of tryCollectImportsDeep(stylable, meta)) { registerInvalidation(depFilePath, filePath); } @@ -269,9 +278,12 @@ export async function build( } function setFileErrorDiagnostic(filePath: string, error: any) { - const diagnostic: Diagnostic = { - type: 'error', + const diagnostic: CLIDiagnostic = { + severity: 'error', message: error instanceof Error ? error.message : String(error), + code: '00000', + node: postcss.root(), + filePath, }; diagnosticsManager.set(identifier, filePath, { @@ -281,12 +293,11 @@ export async function build( } async function buildAggregatedEntities(affectedFiles: Set, generated: Set) { - if (indexFile) { - const indexFilePath = join(fullOutDir, indexFile); - generated.add(indexFilePath); - await generator.generateIndexFile(fs, indexFilePath); + if (indexFileGenerator) { + await indexFileGenerator.generateIndexFile(fs); - outputFiles.set(indexFilePath, affectedFiles); + generated.add(indexFileGenerator.indexFileTargetPath); + outputFiles.set(indexFileGenerator.indexFileTargetPath, affectedFiles); } else { const generatedAssets = handleAssets(assets, projectRoot, srcDir, outDir, fs); for (const generatedAsset of generatedAssets) { diff --git a/packages/cli/src/code-mods/code-mods.ts b/packages/cli/src/code-mods/code-mods.ts index 9d7facd70..44ace794e 100644 --- a/packages/cli/src/code-mods/code-mods.ts +++ b/packages/cli/src/code-mods/code-mods.ts @@ -79,7 +79,7 @@ export function codeMods({ fs, rootDir, extension, mods, log }: CodeModsOptions) function logReports(reports: Map, filePath: string, log: Log) { for (const [name, diagnosticsReports] of reports) { for (const report of diagnosticsReports) { - const error = report.node.error(report.message, report.options); + const error = report.node.error(report.message, { word: report.word }); log(`[${name}]`, `${filePath}: ${report.message}\n${error.showSourceCode()}`); } } diff --git a/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts b/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts index adc17a903..56bec7c31 100644 --- a/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts +++ b/packages/cli/src/code-mods/st-global-custom-property-to-at-property.ts @@ -36,10 +36,10 @@ function parseStGlobalCustomProperty(atRule: AtRule, diagnostics: Diagnostics): .filter((s) => s !== ','); if (cssVarsBySpacing.length > cssVarsByComma.length) { - diagnostics.warn( - atRule, + diagnostics.report( CSSCustomProperty.diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA(atRule.params), { + node: atRule, word: atRule.params, } ); @@ -57,7 +57,8 @@ function parseStGlobalCustomProperty(atRule: AtRule, diagnostics: Diagnostics): alias: undefined, }); } else { - diagnostics.warn(atRule, CSSCustomProperty.diagnostics.ILLEGAL_GLOBAL_CSS_VAR(cssVar), { + diagnostics.report(CSSCustomProperty.diagnostics.ILLEGAL_GLOBAL_CSS_VAR(cssVar), { + node: atRule, word: cssVar, }); } diff --git a/packages/cli/src/config/resolve-options.ts b/packages/cli/src/config/resolve-options.ts index 9371425d3..340e954a2 100644 --- a/packages/cli/src/config/resolve-options.ts +++ b/packages/cli/src/config/resolve-options.ts @@ -102,7 +102,8 @@ export function getCliArguments(): Arguments { }) .option('indexFile', { type: 'string', - description: 'filename of the generated index', + description: + 'filename of the generated index, the index file will reference Stylable sources from the `srcDir` unless the `outputSources` option is `true` in which case it will reference the `outDir`', defaultDescription: String(defaults.indexFile), }) .option('manifest', { @@ -201,7 +202,7 @@ export function createDefaultOptions(): BuildOptions { return { outDir: '.', srcDir: '.', - cjs: true, + cjs: false, esm: false, dts: false, injectCSSRequest: false, @@ -262,3 +263,14 @@ export function createBuildIdentifier( ? projectRoot.replace(rootDir, '') : projectRoot; } + +export function hasStylableCSSOutput(options: BuildOptions): boolean { + return ( + options.cjs || + options.esm || + options.outputCSS || + options.outputSources || + options.dts || + Boolean(options.indexFile) + ); +} diff --git a/packages/cli/src/diagnostics-manager.ts b/packages/cli/src/diagnostics-manager.ts index 496d43324..6ec0e535d 100644 --- a/packages/cli/src/diagnostics-manager.ts +++ b/packages/cli/src/diagnostics-manager.ts @@ -1,10 +1,10 @@ import { createDefaultLogger, Log } from './logger'; -import { Diagnostic, DiagnosticMessages, reportDiagnostics } from './report-diagnostics'; +import { CLIDiagnostic, DiagnosticMessages, reportDiagnostics } from './report-diagnostics'; export type DiagnosticsMode = 'strict' | 'loose'; interface ProcessDiagnostics { - diagnostics: Diagnostic[]; + diagnostics: CLIDiagnostic[]; diagnosticsMode?: DiagnosticsMode | undefined; } @@ -68,7 +68,7 @@ export class DiagnosticsManager { public report() { let diagnosticMode: DiagnosticsMode = 'loose'; const diagnosticMessages: DiagnosticMessages = new Map(); - const collectedDiagnostics = new Map>(); + const collectedDiagnostics = new Map>(); for (const buildDiagnostics of this.store.values()) { for (const [ @@ -88,7 +88,7 @@ export class DiagnosticsManager { const ids = collectedDiagnostics.get(filePath)!; for (const diagnostic of diagnostics) { - const diagnosticId = `${diagnostic.type};${diagnostic.message}`; + const diagnosticId = `${diagnostic.severity};${diagnostic.message}`; if (!ids.has(diagnosticId)) { ids.set(diagnosticId, diagnostic); currentDiagnostics.push(ids.get(diagnosticId)!); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index bf8c295f6..56438b412 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -2,7 +2,7 @@ export { build } from './build'; export { Log, createLogger } from './logger'; export { IndexGenerator, - IndexGenerator as Generator, + IndexGeneratorParameters, ReExports, reExportsAllSymbols, } from './base-generator'; diff --git a/packages/cli/src/report-diagnostics.ts b/packages/cli/src/report-diagnostics.ts index 1c9b8d272..a214a913a 100644 --- a/packages/cli/src/report-diagnostics.ts +++ b/packages/cli/src/report-diagnostics.ts @@ -1,15 +1,13 @@ -import type { DiagnosticType } from '@stylable/core'; +import type { Diagnostic } from '@stylable/core'; import { levels, Log } from './logger'; -export interface Diagnostic { - message: string; - type: DiagnosticType; +export interface CLIDiagnostic extends Diagnostic { line?: number; column?: number; offset?: number; } -export type DiagnosticMessages = Map; +export type DiagnosticMessages = Map; export function reportDiagnostics( log: Log, @@ -20,7 +18,7 @@ export function reportDiagnostics( for (const [filePath, diagnostics] of diagnosticsMessages.entries()) { message += `\n[${filePath}]\n${diagnostics .sort(({ offset: a = 0 }, { offset: b = 0 }) => a - b) - .map(({ type, message }) => `[${type}]: ${message}`) + .map(({ code, severity, message }) => `[${severity}: ${code}]: ${message}`) .join('\n')}`; } @@ -32,7 +30,7 @@ export function reportDiagnostics( function hasErrorOrWarning(diagnosticsMessages: DiagnosticMessages) { for (const diagnostics of diagnosticsMessages.values()) { const has = diagnostics.some( - (diagnostic) => diagnostic.type === 'error' || diagnostic.type === 'warning' + (diagnostic) => diagnostic.severity === 'error' || diagnostic.severity === 'warning' ); if (has) { diff --git a/packages/cli/src/stc-builder.ts b/packages/cli/src/stc-builder.ts index e3a8d406d..d14ad2722 100644 --- a/packages/cli/src/stc-builder.ts +++ b/packages/cli/src/stc-builder.ts @@ -7,8 +7,11 @@ import type { IFileSystem } from '@file-services/types'; import type { DiagnosticMessages } from './report-diagnostics'; import type { STCProjects } from './types'; import type { WatchHandler } from './watch-handler'; -import type { DiagnosticsMode, EmitDiagnosticsContext } from '@stylable/core'; -import { reportDiagnostic } from '@stylable/core/dist/report-diagnostic'; +import { + DiagnosticsMode, + EmitDiagnosticsContext, + reportDiagnostic, +} from '@stylable/core/dist/index-internal'; export type STCBuilderFileSystem = Pick; diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index 573dcc469..4287cce64 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -117,7 +117,7 @@ export interface BuildOptions { outDir: string; /** should the build need to output manifest file */ manifest?: string; - /** opt into build index file and specify the filepath for the generated index file */ + /** generates Stylable index file for the given name, the index file will reference Stylable sources from the `srcDir` unless the `outputSources` option is `true` in which case it will reference the `outDir` */ indexFile?: string; /** custom cli index generator class */ IndexGenerator?: typeof IndexGenerator; diff --git a/packages/cli/src/watch-handler.ts b/packages/cli/src/watch-handler.ts index 441f22b6f..d81e10399 100644 --- a/packages/cli/src/watch-handler.ts +++ b/packages/cli/src/watch-handler.ts @@ -1,5 +1,6 @@ import type { IFileSystem, IWatchEvent, WatchEventListener } from '@file-services/types'; -import type { Stylable, StylableResolverCache } from '@stylable/core'; +import type { Stylable } from '@stylable/core'; +import type { StylableResolverCache } from '@stylable/core/dist/index-internal'; import type { BuildContext } from './types'; import decache from 'decache'; import { diff --git a/packages/cli/test/build.spec.ts b/packages/cli/test/build.spec.ts index 32e4b8fe3..04ee48bdc 100644 --- a/packages/cli/test/build.spec.ts +++ b/packages/cli/test/build.spec.ts @@ -1,9 +1,15 @@ import { expect } from 'chai'; -import { Stylable, processorWarnings, murmurhash3_32_gc } from '@stylable/core'; +import { Stylable } from '@stylable/core'; import { build } from '@stylable/cli'; import { createMemoryFs } from '@file-services/memory'; import { DiagnosticsManager } from '@stylable/cli/dist/diagnostics-manager'; import { STImport, STVar } from '@stylable/core/dist/features'; +import { processorDiagnostics, murmurhash3_32_gc } from '@stylable/core/dist/index-internal'; +import { diagnosticBankReportToStrings } from '@stylable/core-test-kit'; + +const stImportDiagnostics = diagnosticBankReportToStrings(STImport.diagnostics); +const stVarDiagnostics = diagnosticBankReportToStrings(STVar.diagnostics); +const processorStringDiagnostics = diagnosticBankReportToStrings(processorDiagnostics); const log = () => { /**/ @@ -29,7 +35,11 @@ describe('build stand alone', () => { `, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { @@ -79,7 +89,7 @@ describe('build stand alone', () => { `, }); - const stylable = Stylable.create({ + const stylable = new Stylable({ projectRoot: '/', fileSystem: fs, resolveNamespace(n, s) { @@ -159,7 +169,11 @@ describe('build stand alone', () => { `, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); const diagnosticsManager = new DiagnosticsManager(); await build( @@ -182,12 +196,12 @@ describe('build stand alone', () => { const messages = diagnosticsManager.get(identifier, '/comp.st.css')!.diagnostics; expect(messages[0].message).to.contain( - processorWarnings.CANNOT_RESOLVE_EXTEND('MissingComp') + processorStringDiagnostics.CANNOT_RESOLVE_EXTEND('MissingComp') ); expect(messages[1].message).to.contain( - STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./missing-file.st.css') + stImportDiagnostics.UNKNOWN_IMPORTED_FILE('./missing-file.st.css') ); - expect(messages[2].message).to.contain(STVar.diagnostics.UNKNOWN_VAR('missingVar')); + expect(messages[2].message).to.contain(stVarDiagnostics.UNKNOWN_VAR('missingVar')); }); it('should optimize css (remove empty nodes, remove stylable-directives, remove comments)', async () => { @@ -203,7 +217,11 @@ describe('build stand alone', () => { `, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { @@ -238,7 +256,7 @@ describe('build stand alone', () => { `, }); - const stylable = Stylable.create({ + const stylable = new Stylable({ projectRoot: '/', fileSystem: fs, resolveNamespace() { @@ -278,7 +296,11 @@ describe('build stand alone', () => { `, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { @@ -311,7 +333,11 @@ describe('build stand alone', () => { .part {}`, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { @@ -349,7 +375,11 @@ describe('build stand alone', () => { .enum { -st-states: z(enum(on, off, default)); }`, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { @@ -401,7 +431,11 @@ describe('build stand alone', () => { .enum { -st-states: z(enum(on, off, default)); }`, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { diff --git a/packages/cli/test/cli.spec.ts b/packages/cli/test/cli.spec.ts index ba882ad4e..9bc18ea66 100644 --- a/packages/cli/test/cli.spec.ts +++ b/packages/cli/test/cli.spec.ts @@ -10,6 +10,9 @@ import { ITempDirectory, } from '@stylable/e2e-test-kit'; import { STImport, STVar } from '@stylable/core/dist/features'; +import { diagnosticBankReportToStrings } from '@stylable/core-test-kit'; + +const stVarDiagnostics = diagnosticBankReportToStrings(STVar.diagnostics); describe('Stylable Cli', function () { this.timeout(25000); @@ -29,7 +32,7 @@ describe('Stylable Cli', function () { 'style.st.css': `.root{color:red}`, }); - runCliSync(['--rootDir', tempDir.path, '--nsr', testNsrPath]); + runCliSync(['--rootDir', tempDir.path, '--nsr', testNsrPath, '--cjs']); const dirContent = loadDirSync(tempDir.path); expect( @@ -46,7 +49,15 @@ describe('Stylable Cli', function () { 'style.st.css': `.root{color:red}`, }); - runCliSync(['--rootDir', tempDir.path, '--nsr', testNsrPath, '--outDir', './dist']); + runCliSync([ + '--rootDir', + tempDir.path, + '--nsr', + testNsrPath, + '--outDir', + './dist', + '--cjs', + ]); const dirContent = loadDirSync(tempDir.path); expect(Object.keys(dirContent)).to.eql([ @@ -110,7 +121,7 @@ describe('Stylable Cli', function () { }); const nsr = require.resolve('@stylable/node'); - runCliSync(['--rootDir', tempDir.path, '--nsr', nsr]); + runCliSync(['--rootDir', tempDir.path, '--nsr', nsr, '--cjs']); const dirContent = loadDirSync(tempDir.path); @@ -168,6 +179,36 @@ describe('Stylable Cli', function () { ).to.equal(true); }); + it('build only .st.css.d.ts and .st.css.d.ts.map files ', () => { + const srcContent = '.root{color:red}'; + populateDirectorySync(tempDir.path, { + 'package.json': `{"name": "test", "version": "0.0.0"}`, + 'style.st.css': srcContent, + }); + + const { stdout, status } = runCliSync([ + '--rootDir', + tempDir.path, + '--outDir', + 'dist', + '--dts', + ]); + + const dirContent = loadDirSync(tempDir.path); + const dtsContent = dirContent['dist/style.st.css.d.ts']; + const dtsSourceMapContent = dirContent['dist/style.st.css.d.ts.map']; + + expect(dtsContent.startsWith('/* THIS FILE IS AUTO GENERATED DO NOT MODIFY */')).to.equal( + true + ); + expect( + dtsSourceMapContent.startsWith('{\n "version": 3,\n "file": "style.st.css.d.ts"') + ).to.equal(true); + + expect(status).to.equal(0); + expect(stdout, 'stdout').to.not.match(new RegExp(`No target output declared for "(.*?)"`)); + }); + it('build .st.css.d.ts source-map and target the source file path relatively', () => { const srcContent = '.root{color:red}'; populateDirectorySync(tempDir.path, { @@ -356,6 +397,22 @@ describe('Stylable Cli', function () { expect(stdout, 'stdout').to.match(/unknown var "xxx"/); }); + it('should report when there are no css output formats', () => { + populateDirectorySync(tempDir.path, { + 'package.json': `{"name": "test", "version": "0.0.0"}`, + 'style.st.css': `.root{}`, + }); + + const { stdout, status } = runCliSync(['--rootDir', tempDir.path]); + + expect(status).to.equal(0); + expect(stdout, 'stdout').to.match( + new RegExp( + `No target output declared for "(.*?)", please provide one or more of the following target options: "cjs", "esm", "css", "stcss" or "indexFile"` + ) + ); + }); + it('(diagnosticsMode) should not exit with error when using strict mode with only info diagnostics', () => { populateDirectorySync(tempDir.path, { 'package.json': `{"name": "test", "version": "0.0.0"}`, @@ -373,7 +430,7 @@ describe('Stylable Cli', function () { expect(stdout, 'stdout').to.match(/style\.st\.css/); expect(stdout, 'stdout').to.match( new RegExp( - `\\[info\\]: ${STVar.diagnostics.DEPRECATED_ST_FUNCTION_NAME( + `\\[info\\: \\d+]: ${stVarDiagnostics.DEPRECATED_ST_FUNCTION_NAME( 'stArray', 'st-array' )}` @@ -439,7 +496,9 @@ describe('Stylable Cli', function () { expect(status).to.equal(1); expect( - stdout.match(new RegExp(STImport.diagnostics.NO_ST_IMPORT_IN_NESTED_SCOPE(), 'g')) + stdout.match( + new RegExp(STImport.diagnostics.NO_ST_IMPORT_IN_NESTED_SCOPE().message, 'g') + ) ).to.have.length(1); }); diff --git a/packages/cli/test/codemods/st-global-custom-property-to-at-property.spec.ts b/packages/cli/test/codemods/st-global-custom-property-to-at-property.spec.ts index 503f2d51f..823bb0265 100644 --- a/packages/cli/test/codemods/st-global-custom-property-to-at-property.spec.ts +++ b/packages/cli/test/codemods/st-global-custom-property-to-at-property.spec.ts @@ -7,6 +7,9 @@ import { createTempDirectory, ITempDirectory, } from '@stylable/e2e-test-kit'; +import { diagnosticBankReportToStrings } from '@stylable/core-test-kit'; + +const cssCustomPropertyDiagnostics = diagnosticBankReportToStrings(CSSCustomProperty.diagnostics); describe('CLI Codemods st-global-custom-property-to-at-property', () => { let tempDir: ITempDirectory; @@ -73,7 +76,7 @@ describe('CLI Codemods st-global-custom-property-to-at-property', () => { expect(stdout).to.match( new RegExp( - `style.st.css: ${CSSCustomProperty.diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA( + `style.st.css: ${cssCustomPropertyDiagnostics.GLOBAL_CSS_VAR_MISSING_COMMA( '--myVar --mySecondVar' )}` ) diff --git a/packages/cli/test/codemods/st-import-to-at-import.spec.ts b/packages/cli/test/codemods/st-import-to-at-import.spec.ts index cbdc62ce6..9cac8f4f9 100644 --- a/packages/cli/test/codemods/st-import-to-at-import.spec.ts +++ b/packages/cli/test/codemods/st-import-to-at-import.spec.ts @@ -51,6 +51,6 @@ describe('CLI Codemods st-import-to-at-import', () => { expect(dirContent['style.st.css']).equal( `:import {-st-from: './x.st.css'; -st-from: './y.st.css';}` ); - expect(stdout).to.match(new RegExp(parseImportMessages.MULTIPLE_FROM_IN_IMPORT())); + expect(stdout).to.match(new RegExp(parseImportMessages.MULTIPLE_FROM_IN_IMPORT().message)); }); }); diff --git a/packages/cli/test/config-options.spec.ts b/packages/cli/test/config-options.spec.ts index 8c7c0d155..08431294e 100644 --- a/packages/cli/test/config-options.spec.ts +++ b/packages/cli/test/config-options.spec.ts @@ -112,9 +112,12 @@ describe('Stylable CLI config file options', function () { 'package.json': `{"name": "test", "version": "0.0.0"}`, 'style.st.css': `.root{color:red}`, 'stylable.config.js': ` - exports.stcConfig = () => ({ options: { - outDir: './out', - } }) + exports.stcConfig = () => ({ + options: { + cjs: true, + outDir: './out', + } + }) `, }); @@ -138,9 +141,12 @@ describe('Stylable CLI config file options', function () { 'package.json': `{"name": "test", "version": "0.0.0"}`, 'style.st.css': `.root{color:red}`, 'stylable.config.js': ` - exports.stcConfig = () => ({ options: { - outDir: './dist', - } }) + exports.stcConfig = () => ({ + options: { + cjs: true, + outDir: './dist', + } + }) `, }, }); diff --git a/packages/cli/test/config-projects.spec.ts b/packages/cli/test/config-projects.spec.ts index b1432d086..359df288f 100644 --- a/packages/cli/test/config-projects.spec.ts +++ b/packages/cli/test/config-projects.spec.ts @@ -9,6 +9,9 @@ import { ITempDirectory, } from '@stylable/e2e-test-kit'; import { STVar } from '@stylable/core/dist/features'; +import { diagnosticBankReportToStrings } from '@stylable/core-test-kit'; + +const stVarDiagnostics = diagnosticBankReportToStrings(STVar.diagnostics); describe('Stylable CLI config multiple projects', function () { this.timeout(25000); @@ -472,13 +475,15 @@ describe('Stylable CLI config multiple projects', function () { const { stdout } = runCliSync(['--rootDir', tempDir.path]); const firstError = stdout.indexOf('error 1'); - const stylableError = stdout.indexOf(STVar.diagnostics.UNKNOWN_VAR('unknown')); + const stylableError = stdout.indexOf(stVarDiagnostics.UNKNOWN_VAR('unknown')); const secondError = stdout.indexOf('error 2'); expect(firstError, 'sorted by location') .to.be.lessThan(stylableError) .and.lessThan(secondError); - expect(stdout.match(STVar.diagnostics.UNKNOWN_VAR('unknown'))?.length).to.eql(1); + expect(stdout.match(STVar.diagnostics.UNKNOWN_VAR('unknown').message)?.length).to.eql( + 1 + ); }); it('should throw when the property "projects" is invalid', () => { diff --git a/packages/cli/test/fixtures/named-exports-generator.ts b/packages/cli/test/fixtures/named-exports-generator.ts index 27dc24243..d88b1cd6b 100644 --- a/packages/cli/test/fixtures/named-exports-generator.ts +++ b/packages/cli/test/fixtures/named-exports-generator.ts @@ -1,11 +1,11 @@ -import { Generator as Base, ReExports, reExportsAllSymbols } from '@stylable/cli'; +import { IndexGenerator, ReExports, reExportsAllSymbols } from '@stylable/cli'; -export class Generator extends Base { +export class Generator extends IndexGenerator { public generateReExports(filePath: string): ReExports { return reExportsAllSymbols(filePath, this); } - protected generateIndexSource(indexFileTargetPath: string) { - const source = super.generateIndexSource(indexFileTargetPath); + protected generateIndexSource() { + const source = super.generateIndexSource(); return '@namespace "INDEX";\n' + source; } } diff --git a/packages/cli/test/fixtures/test-generator.ts b/packages/cli/test/fixtures/test-generator.ts index 27ae1cd57..7bc4cc036 100644 --- a/packages/cli/test/fixtures/test-generator.ts +++ b/packages/cli/test/fixtures/test-generator.ts @@ -1,6 +1,6 @@ -import { Generator as Base, ReExports } from '@stylable/cli'; +import { IndexGenerator, ReExports } from '@stylable/cli'; -export class Generator extends Base { +export class Generator extends IndexGenerator { private count = 0; public generateReExports(filePath: string): ReExports | undefined { if (filePath.includes('FILTER-ME')) { diff --git a/packages/cli/test/generate-index.spec.ts b/packages/cli/test/generate-index.spec.ts index 068068fd3..0ba9dc892 100644 --- a/packages/cli/test/generate-index.spec.ts +++ b/packages/cli/test/generate-index.spec.ts @@ -19,7 +19,11 @@ describe('build index', () => { `, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { @@ -47,6 +51,97 @@ describe('build index', () => { ].join('\n') ); }); + it('should create index file importing all matched stylesheets in outDir (outputSources)', async () => { + const fs = createMemoryFs({ + src: { + '/compA.st.css': ` + .a{} + `, + '/a/b/comp-B.st.css': ` + .b{} + `, + }, + }); + + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); + + await build( + { + outDir: './dist', + srcDir: './src', + indexFile: 'index.st.css', + outputSources: true, + }, + { + fs, + stylable, + rootDir: '/', + projectRoot: '/', + log, + } + ); + + const res = fs.readFileSync('./dist/index.st.css').toString(); + + expect(res.trim()).to.equal( + [ + ':import {-st-from: "./compA.st.css";-st-default:CompA;}', + '.root CompA{}', + ':import {-st-from: "./a/b/comp-B.st.css";-st-default:CompB;}', + '.root CompB{}', + ].join('\n') + ); + }); + + it('should create index file importing all matched stylesheets in srcDir when has output cjs files', async () => { + const fs = createMemoryFs({ + src: { + '/compA.st.css': ` + .a{} + `, + '/a/b/comp-B.st.css': ` + .b{} + `, + }, + }); + + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); + + await build( + { + outDir: '.', + srcDir: '.', + indexFile: './dist/index.st.css', + cjs: true, + }, + { + fs, + stylable, + rootDir: '/', + projectRoot: '/', + log, + } + ); + + const res = fs.readFileSync('/dist/index.st.css').toString(); + + expect(res.trim()).to.equal( + [ + ':import {-st-from: "../src/compA.st.css";-st-default:CompA;}', + '.root CompA{}', + ':import {-st-from: "../src/a/b/comp-B.st.css";-st-default:CompB;}', + '.root CompB{}', + ].join('\n') + ); + }); it('should create index file using a the default generator', async () => { const fs = createMemoryFs({ '/comp-A.st.css': ` @@ -57,7 +152,11 @@ describe('build index', () => { `, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { @@ -95,7 +194,11 @@ describe('build index', () => { `, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { @@ -124,7 +227,6 @@ describe('build index', () => { ].join('\n') ); }); - it('should create index file when srcDir is parent directory of outDir', async () => { const fs = createMemoryFs({ dist: { @@ -137,7 +239,11 @@ describe('build index', () => { }, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { @@ -167,7 +273,6 @@ describe('build index', () => { ].join('\n') ); }); - it('custom generator is able to filter files from the index', async () => { const fs = createMemoryFs({ '/comp-A.st.css': ` @@ -178,7 +283,11 @@ describe('build index', () => { `, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { @@ -202,7 +311,6 @@ describe('build index', () => { ':import {-st-from: "./comp-A.st.css";-st-default:Style0;}\n.root Style0{}' ); }); - it('should create index file using a custom generator with named exports generation and @namespace', async () => { const fs = createMemoryFs({ '/comp-A.st.css': ` @@ -219,7 +327,11 @@ describe('build index', () => { `, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { @@ -251,7 +363,6 @@ describe('build index', () => { ].join('\n') ); }); - it('should create non-existing folders in path to the generated indexFile', async () => { const fs = createMemoryFs({ '/comp.st.css': ` @@ -259,7 +370,11 @@ describe('build index', () => { `, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); await build( { outDir: './some-dir/other-dir/', @@ -291,7 +406,11 @@ describe('build index', () => { `, }); - const stylable = new Stylable('/', fs, () => ({})); + const stylable = new Stylable({ + projectRoot: '/', + fileSystem: fs, + requireModule: () => ({}), + }); const diagnosticsManager = new DiagnosticsManager(); await build( diff --git a/packages/cli/test/tsconfig.json b/packages/cli/test/tsconfig.json index c67199794..269791701 100644 --- a/packages/cli/test/tsconfig.json +++ b/packages/cli/test/tsconfig.json @@ -6,6 +6,7 @@ }, "references": [ { "path": "../../core/src" }, + { "path": "../../core-test-kit/src" }, { "path": "../../e2e-test-kit/src" }, { "path": "../src" }, { "path": "../../module-utils/src" }, diff --git a/packages/cli/test/watch-multiple-projects.spec.ts b/packages/cli/test/watch-multiple-projects.spec.ts index d70547b06..cdeabab38 100644 --- a/packages/cli/test/watch-multiple-projects.spec.ts +++ b/packages/cli/test/watch-multiple-projects.spec.ts @@ -390,7 +390,7 @@ describe('Stylable Cli Watch - Multiple projects', function () { ), }, { - msg: '[error]: nesting of rules within rules is not supported', + msg: '[error: 11011]: nesting of rules within rules is not supported', action() { return writeToExistingFile( join(tempDir.path, 'packages', 'project-a', 'style.st.css'), @@ -472,7 +472,8 @@ describe('Stylable Cli Watch - Multiple projects', function () { ), }, { - msg: STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css'), + msg: STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css') + .message, }, ], }); @@ -525,7 +526,8 @@ describe('Stylable Cli Watch - Multiple projects', function () { args: ['-w'], steps: [ { - msg: STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css'), + msg: STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css') + .message, }, { msg: buildMessages.START_WATCHING(), @@ -534,7 +536,9 @@ describe('Stylable Cli Watch - Multiple projects', function () { }); expect( - output().match(STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css')) + output().match( + STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css').message + ) ).to.lengthOf(1); }); }); diff --git a/packages/cli/test/watch-single-project.spec.ts b/packages/cli/test/watch-single-project.spec.ts index a12771c16..62c39d4ea 100644 --- a/packages/cli/test/watch-single-project.spec.ts +++ b/packages/cli/test/watch-single-project.spec.ts @@ -10,7 +10,7 @@ import { ITempDirectory, } from '@stylable/e2e-test-kit'; import { expect } from 'chai'; -import { realpathSync, renameSync, rmdirSync, unlinkSync, promises } from 'fs'; +import { realpathSync, renameSync, rmSync, unlinkSync, promises } from 'fs'; import { join, sep } from 'path'; const { writeFile } = promises; @@ -208,7 +208,7 @@ describe('Stylable Cli Watch - Single project', function () { { msg: buildMessages.START_WATCHING(), action() { - rmdirSync(join(tempDir.path, 'styles'), { recursive: true }); + rmSync(join(tempDir.path, 'styles'), { recursive: true }); }, }, { @@ -237,7 +237,7 @@ describe('Stylable Cli Watch - Single project', function () { { msg: buildMessages.START_WATCHING(), action() { - rmdirSync(join(tempDir.path, 'styles'), { recursive: true }); + rmSync(join(tempDir.path, 'styles'), { recursive: true }); }, }, { @@ -303,7 +303,8 @@ describe('Stylable Cli Watch - Single project', function () { args: ['--outDir', './dist', '-w', '--cjs', '--css'], steps: [ { - msg: STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css'), + msg: STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css') + .message, }, { msg: buildMessages.START_WATCHING(), @@ -335,7 +336,8 @@ describe('Stylable Cli Watch - Single project', function () { args: ['--outDir', './dist', '-w', '--cjs', '--css'], steps: [ { - msg: STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css'), + msg: STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css') + .message, }, { msg: buildMessages.START_WATCHING(), @@ -352,7 +354,8 @@ describe('Stylable Cli Watch - Single project', function () { }, }, { - msg: STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css'), + msg: STImport.diagnostics.UNKNOWN_IMPORTED_FILE('./does-not-exist.st.css') + .message, }, ], }); diff --git a/packages/code-formatter/package.json b/packages/code-formatter/package.json index 3de856d22..ac7d19b3c 100644 --- a/packages/code-formatter/package.json +++ b/packages/code-formatter/package.json @@ -1,13 +1,13 @@ { "name": "@stylable/code-formatter", - "version": "4.14.0", + "version": "5.0.0", "description": "A code formatting utility for Stylable stylesheets", "main": "dist/index.js", "scripts": { "test": "mocha \"./dist/test/**/*.spec.js\"" }, "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "js-beautify": "^1.14.4" }, "files": [ @@ -17,7 +17,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/core-test-kit/package.json b/packages/core-test-kit/package.json index 4a4570f6e..a617fbdf6 100644 --- a/packages/core-test-kit/package.json +++ b/packages/core-test-kit/package.json @@ -1,6 +1,6 @@ { "name": "@stylable/core-test-kit", - "version": "4.14.0", + "version": "5.0.0", "description": "Stylable core test-kit", "main": "dist/index.js", "scripts": { @@ -8,7 +8,7 @@ }, "dependencies": { "@file-services/memory": "^6.0.0", - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "chai": "^4.3.6", "flat": "^5.0.2", "postcss": "^8.4.14" @@ -20,7 +20,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/core-test-kit/src/diagnostics.ts b/packages/core-test-kit/src/diagnostics.ts index 78591f3e1..d468f5105 100644 --- a/packages/core-test-kit/src/diagnostics.ts +++ b/packages/core-test-kit/src/diagnostics.ts @@ -1,12 +1,12 @@ import { expect } from 'chai'; import deindent from 'deindent'; import type { Position } from 'postcss'; -import { Diagnostics, DiagnosticType, StylableMeta, StylableResults } from '@stylable/core'; -import { safeParse, process } from '@stylable/core/dist/index-internal'; +import { Diagnostics, DiagnosticSeverity, StylableMeta, StylableResults } from '@stylable/core'; +import { DiagnosticBase, safeParse, StylableProcessor } from '@stylable/core/dist/index-internal'; import { Config, generateStylableResult } from './generate-test-util'; export interface Diagnostic { - severity?: DiagnosticType; + severity?: DiagnosticSeverity; message: string; file: string; skipLocationCheck?: boolean; @@ -32,7 +32,8 @@ const createMatchDiagnosticState = (): MatchState => ({ word: ``, severity: ``, }); -const isSupportedSeverity = (val: string): val is DiagnosticType => !!val.match(/info|warn|error/); +const isSupportedSeverity = (val: string): val is DiagnosticSeverity => + !!val.match(/info|warn|error/); export function matchDiagnostic( type: `analyze` | `transform`, meta: Pick, @@ -94,7 +95,7 @@ export function matchDiagnostic( matchState.matches++; // } if (expected.location.word) { - if (report.options.word !== expected.location.word) { + if (report.word !== expected.location.word) { matchState.word = errors.wordMismatch( type, expected.location.word, @@ -107,11 +108,11 @@ export function matchDiagnostic( matchState.matches++; } if (expected.severity) { - if (report.type !== expectedSeverity) { + if (report.severity !== expectedSeverity) { matchState.location = errors.severityMismatch( type, expectedSeverity, - report.type, + report.severity, expected.message, expected.label ); @@ -170,7 +171,7 @@ export function expectAnalyzeDiagnostics( ) { const source = findTestLocations(css); const root = safeParse(source.css); - const res = process(root); + const res = new StylableProcessor(new Diagnostics()).process(root); if (partial) { if (warnings.length === 0) { @@ -191,13 +192,13 @@ export function expectAnalyzeDiagnostics( expect(report.node.source!.start, 'start').to.eql(source.start); } if (source.word !== null) { - expect(report.options.word).to.equal(source.word); + expect(report.word).to.equal(source.word); } if (expectedWarning.severity) { expect( - report.type, - `diagnostics severity mismatch, expected "${expectedWarning.severity}" but received "${report.type}"` + report.severity, + `diagnostics severity mismatch, expected "${expectedWarning.severity}" but received "${report.severity}"` ).to.equal(expectedWarning.severity); } }); @@ -229,13 +230,13 @@ function matchPartialDiagnostics( matches++; } if (locations[path].word !== null) { - expect(report.options.word).to.eql(locations[path].word); + expect(report.word).to.eql(locations[path].word); matches++; } if (expectedWarning.severity) { expect( - report.type, - `${report.message}: severity mismatch, expected ${expectedWarning.severity} but received ${report.type}` + report.severity, + `${report.message}: severity mismatch, expected ${expectedWarning.severity} but received ${report.severity}` ).to.equal(expectedWarning.severity); matches++; } @@ -297,13 +298,13 @@ export function expectTransformDiagnostics( } if (locations[path].word !== null) { - expect(report.options.word).to.eql(locations[path].word); + expect(report.word).to.eql(locations[path].word); } if (expectedWarning.severity) { expect( - report.type, - `diagnostics severity mismatch, expected ${expectedWarning.severity} but received ${report.type}` + report.severity, + `diagnostics severity mismatch, expected ${expectedWarning.severity} but received ${report.severity}` ).to.equal(expectedWarning.severity); } } @@ -336,3 +337,23 @@ export function shouldReportNoDiagnostics(meta: StylableMeta, checkTransformDiag ).to.equal(0); } } + +export type DiagnosticsBank = Record DiagnosticBase>; + +export type UnwrapDiagnosticMessage = { + [K in keyof T]: (...args: Parameters) => string; +}; + +export function diagnosticBankReportToStrings( + bank: T +): UnwrapDiagnosticMessage { + const cleaned = {} as UnwrapDiagnosticMessage; + + for (const [diagName, diagFunc] of Object.entries(bank)) { + cleaned[diagName as keyof T] = (...args) => { + return diagFunc(...args).message; + }; + } + + return cleaned; +} diff --git a/packages/core-test-kit/src/generate-test-util.ts b/packages/core-test-kit/src/generate-test-util.ts index 23bb019ee..a94af7f17 100644 --- a/packages/core-test-kit/src/generate-test-util.ts +++ b/packages/core-test-kit/src/generate-test-util.ts @@ -1,18 +1,21 @@ import { Diagnostics, - FileProcessor, - postProcessor, processNamespace, - replaceValueHook, StylableMeta, - StylableResolver, - createStylableFileProcessor, - createDefaultResolver, Stylable, StylableConfig, + createDefaultResolver, } from '@stylable/core'; import { createJavascriptRequireModule } from './test-stylable-core'; -import { process, StylableTransformer } from '@stylable/core/dist/index-internal'; +import { + FileProcessor, + StylableProcessor, + StylableResolver, + StylableTransformer, + createStylableFileProcessor, + postProcessor, + replaceValueHook, +} from '@stylable/core/dist/index-internal'; import { isAbsolute } from 'path'; import * as postcss from 'postcss'; import { createMemoryFs } from '@file-services/memory'; @@ -104,7 +107,9 @@ export function processSource( options: { from?: string } = {}, resolveNamespace?: typeof processNamespace ) { - return process(postcss.parse(source, options), undefined, resolveNamespace); + return new StylableProcessor(new Diagnostics(), resolveNamespace).process( + postcss.parse(source, options) + ); } export function createProcess( @@ -126,7 +131,7 @@ export function generateStylableResult( } export function generateStylableRoot(config: Config) { - return generateStylableResult(config).meta.outputAst!; + return generateStylableResult(config).meta.targetAst!; } export function generateStylableExports(config: Config) { @@ -139,7 +144,7 @@ export function generateStylableEnvironment( ) { const fs = createMemoryFs(content); - const stylable = Stylable.create({ + const stylable = new Stylable({ fileSystem: fs, projectRoot: '/', resolveNamespace: (ns) => ns, diff --git a/packages/core-test-kit/src/index.ts b/packages/core-test-kit/src/index.ts index 5694d0128..c1f2a3ac3 100644 --- a/packages/core-test-kit/src/index.ts +++ b/packages/core-test-kit/src/index.ts @@ -4,6 +4,7 @@ export { expectTransformDiagnostics, findTestLocations, shouldReportNoDiagnostics, + diagnosticBankReportToStrings, } from './diagnostics'; export { Config, diff --git a/packages/core-test-kit/src/inline-expectation.ts b/packages/core-test-kit/src/inline-expectation.ts index 5acdfcc13..64ca37f7b 100644 --- a/packages/core-test-kit/src/inline-expectation.ts +++ b/packages/core-test-kit/src/inline-expectation.ts @@ -23,7 +23,7 @@ const testScopes = Object.keys(tests) as TestScopes[]; const testScopesRegex = () => testScopes.join(`|`); interface Context { - meta: Pick; + meta: Pick; } const isRoot = (val: any): val is postcss.Root => val.type === `root`; @@ -65,14 +65,14 @@ export function testInlineExpects(result: postcss.Root | Context, expectedTestIn const context = isDeprecatedInput ? { meta: { - outputAst: result, - rawAst: result, + sourceAst: result, + targetAst: result, diagnostics: null as unknown as StylableMeta['diagnostics'], transformDiagnostics: null as unknown as StylableMeta['transformDiagnostics'], }, } : result; - const rootAst = context.meta.rawAst; + const rootAst = context.meta.sourceAst; const expectedTestAmount = expectedTestInput ?? (rootAst.toString().match(new RegExp(`${testScopesRegex()}`, `gm`))?.length || 0); @@ -427,10 +427,10 @@ function diagnosticTest( function getTargetComment(meta: Context['meta'], { source }: postcss.Comment) { let match: postcss.Comment | undefined = undefined; - if (!meta.outputAst) { + if (!meta.targetAst) { return; } - meta.outputAst.walkComments((outputComment) => { + meta.targetAst.walkComments((outputComment) => { if ( outputComment.source?.start?.offset === source?.start?.offset && outputComment.source?.end?.offset === source?.end?.offset diff --git a/packages/core-test-kit/src/matchers/results.ts b/packages/core-test-kit/src/matchers/results.ts index c93493bda..2d9b174de 100644 --- a/packages/core-test-kit/src/matchers/results.ts +++ b/packages/core-test-kit/src/matchers/results.ts @@ -16,12 +16,12 @@ export function mediaQuery(chai: Chai.ChaiStatic, util: Chai.ChaiUtils) { ); } - const { outputAst } = actual.meta; - if (!outputAst) { - throw new Error(`expected result to be transformed - missing outputAst on meta`); + const { targetAst } = actual.meta; + if (!targetAst) { + throw new Error(`expected result to be transformed - missing targetAst on meta`); } - const nodes = outputAst.nodes; + const nodes = targetAst.nodes; if (!nodes) { throw new Error(`no rules found for media`); @@ -54,13 +54,13 @@ export function styleRules(chai: Chai.ChaiStatic, util: Chai.ChaiUtils) { let scopeRule: postcss.Container | undefined = flag(this, 'actualRule'); if (!scopeRule) { - const { outputAst } = actual.meta; - if (!outputAst) { + const { targetAst } = actual.meta; + if (!targetAst) { throw new Error( - `expected result to be transfromed - missing outputAst on meta` + `expected result to be transfromed - missing targetAst on meta` ); } else { - scopeRule = outputAst; + scopeRule = targetAst; } } diff --git a/packages/core-test-kit/src/test-stylable-core.ts b/packages/core-test-kit/src/test-stylable-core.ts index c730a32e2..b83ad0ea0 100644 --- a/packages/core-test-kit/src/test-stylable-core.ts +++ b/packages/core-test-kit/src/test-stylable-core.ts @@ -33,7 +33,7 @@ export function testStylableCore( const fs = options.stylableConfig?.filesystem || createMemoryFs(typeof input === `string` ? { '/entry.st.css': input } : input); - const stylable = Stylable.create({ + const stylable = new Stylable({ fileSystem: fs, projectRoot: '/', resolveNamespace: (ns) => ns, @@ -59,7 +59,7 @@ export function testStylableCore( // inline test - build all and test for (const path of allSheets) { const meta = stylable.analyze(path); - if (!meta.outputAst) { + if (!meta.targetAst) { // ToDo: test stylable.transform(meta); } diff --git a/packages/core-test-kit/test/inline-expectations.spec.ts b/packages/core-test-kit/test/inline-expectations.spec.ts index aa0c3fd2a..01f969369 100644 --- a/packages/core-test-kit/test/inline-expectations.spec.ts +++ b/packages/core-test-kit/test/inline-expectations.spec.ts @@ -1,12 +1,19 @@ import { + diagnosticBankReportToStrings, generateStylableResult, generateStylableRoot, testInlineExpects, testInlineExpectsErrors, } from '@stylable/core-test-kit'; -import { transformerWarnings } from '@stylable/core/dist/stylable-transformer'; +import { transformerDiagnostics } from '@stylable/core/dist/stylable-transformer'; import { STImport, CSSType, CSSClass } from '@stylable/core/dist/features'; import { expect } from 'chai'; +import type { Rule } from 'postcss'; + +const cssClassDiagnostics = diagnosticBankReportToStrings(CSSClass.diagnostics); +const cssTypeDiagnostics = diagnosticBankReportToStrings(CSSType.diagnostics); +const stImportDiagnostics = diagnosticBankReportToStrings(STImport.diagnostics); +const transformerStringDiagnostics = diagnosticBankReportToStrings(transformerDiagnostics); describe('inline-expectations', () => { it('should throw when expected amount is not found (manual)', () => { @@ -371,7 +378,7 @@ describe('inline-expectations', () => { }, }); - result.meta.outputAst?.nodes[1].remove(); + result.meta.targetAst?.nodes[1].remove(); expect(() => testInlineExpects(result)).to.throw( testInlineExpectsErrors.removedNode(`rule`, `(label): `) @@ -465,7 +472,7 @@ describe('inline-expectations', () => { }, }); - result.meta.outputAst?.nodes[1].remove(); + result.meta.targetAst?.nodes[1].remove(); expect(() => testInlineExpects(result)).to.throw( testInlineExpectsErrors.removedNode(`atrule`, `(label): `) @@ -608,7 +615,7 @@ describe('inline-expectations', () => { }, }); - (result.meta.outputAst?.nodes[0] as any).nodes[1].remove(); + (result.meta.targetAst!.nodes[0] as Rule).nodes[1].remove(); expect(() => testInlineExpects(result)).to.throw( testInlineExpectsErrors.removedNode(`decl`, `(label): `) @@ -673,13 +680,13 @@ describe('inline-expectations', () => { '/style.st.css': { namespace: 'entry', content: ` - /* @analyze-warn(1 line) ${CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR( + /* @analyze-warn(1 line) ${cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR( `div` )} */ div {} /* @analyze-warn(multi line) word(span) - ${CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR(`span`)} + ${cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR(`span`)} */ span {} `, @@ -725,8 +732,8 @@ describe('inline-expectations', () => { namespace: 'entry', content: ` /* - @analyze-warn ${CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR(`div`)} - @analyze-warn(label) ${CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR( + @analyze-warn ${cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR(`div`)} + @analyze-warn(label) ${cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR( `div` )} */ @@ -742,11 +749,11 @@ describe('inline-expectations', () => { testInlineExpectsErrors.combine([ testInlineExpectsErrors.diagnosticsLocationMismatch( `analyze`, - CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR(`div`) + cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR(`div`) ), testInlineExpectsErrors.diagnosticsLocationMismatch( `analyze`, - CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR(`div`), + cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR(`div`), `(label): ` ), ]) @@ -759,12 +766,12 @@ describe('inline-expectations', () => { '/style.st.css': { namespace: 'entry', content: ` - /* @analyze-warn word(single) ${CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR( + /* @analyze-warn word(single) ${cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR( `div` )} */ div {} - /* @analyze-warn(many words) word(one two) ${CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR( + /* @analyze-warn(many words) word(one two) ${cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR( `div` )} */ div {} @@ -778,12 +785,12 @@ describe('inline-expectations', () => { testInlineExpectsErrors.diagnosticsWordMismatch( `analyze`, `single`, - CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR(`div`) + cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR(`div`) ), testInlineExpectsErrors.diagnosticsWordMismatch( `analyze`, `one two`, - CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR(`div`), + cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR(`div`), `(many words): ` ), ]) @@ -796,10 +803,10 @@ describe('inline-expectations', () => { '/style.st.css': { namespace: 'entry', content: ` - /* @analyze-error ${CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR(`div`)} */ + /* @analyze-error ${cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR(`div`)} */ div {} - /* @analyze-error(label) ${CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR( + /* @analyze-error(label) ${cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR( `div` )} */ div {} @@ -814,13 +821,13 @@ describe('inline-expectations', () => { `analyze`, `error`, `warning`, - CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR(`div`) + cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR(`div`) ), testInlineExpectsErrors.diagnosticsSeverityMismatch( `analyze`, `error`, `warning`, - CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR(`div`), + cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR(`div`), `(label): ` ), ]) @@ -864,10 +871,10 @@ describe('inline-expectations', () => { '/style.st.css': { namespace: 'entry', content: ` - /* @analyze-warn word(comp) ${STImport.diagnostics.DEFAULT_IMPORT_IS_LOWER_CASE()} */ + /* @analyze-warn word(comp) ${stImportDiagnostics.DEFAULT_IMPORT_IS_LOWER_CASE()} */ @st-import comp from "./x.st.css"; - /* @analyze-warn ${STImport.diagnostics.ST_IMPORT_EMPTY_FROM()} */ + /* @analyze-warn ${stImportDiagnostics.ST_IMPORT_EMPTY_FROM()} */ @st-import comp from "./x.st.css"; `, }, @@ -877,7 +884,7 @@ describe('inline-expectations', () => { expect(() => testInlineExpects(result)).to.throw( testInlineExpectsErrors.diagnosticExpectedNotFound( `analyze`, - STImport.diagnostics.ST_IMPORT_EMPTY_FROM() + stImportDiagnostics.ST_IMPORT_EMPTY_FROM() ) ); }); @@ -964,7 +971,7 @@ describe('inline-expectations', () => { @st-import [unknown] from './other.st.css'; .root { - /* @transform-error(1 line) ${CSSClass.diagnostics.CANNOT_EXTEND_UNKNOWN_SYMBOL( + /* @transform-error(1 line) ${cssClassDiagnostics.CANNOT_EXTEND_UNKNOWN_SYMBOL( `unknown` )}*/ -st-extends: unknown; @@ -972,7 +979,7 @@ describe('inline-expectations', () => { .part { /* @transform-error(multi line) - ${CSSClass.diagnostics.CANNOT_EXTEND_UNKNOWN_SYMBOL(`unknown`)}*/ + ${cssClassDiagnostics.CANNOT_EXTEND_UNKNOWN_SYMBOL(`unknown`)}*/ -st-extends: unknown; } `, @@ -1024,10 +1031,10 @@ describe('inline-expectations', () => { @st-import [unknown] from './unknown.st.css'; /* - @transform-warn ${STImport.diagnostics.UNKNOWN_IMPORTED_FILE( + @transform-warn ${stImportDiagnostics.UNKNOWN_IMPORTED_FILE( `./unknown.st.css` )} - @transform-warn(label) ${STImport.diagnostics.UNKNOWN_IMPORTED_FILE( + @transform-warn(label) ${stImportDiagnostics.UNKNOWN_IMPORTED_FILE( `./unknown.st.css` )} */ @@ -1041,11 +1048,11 @@ describe('inline-expectations', () => { testInlineExpectsErrors.combine([ testInlineExpectsErrors.diagnosticsLocationMismatch( `transform`, - STImport.diagnostics.UNKNOWN_IMPORTED_FILE(`./unknown.st.css`) + stImportDiagnostics.UNKNOWN_IMPORTED_FILE(`./unknown.st.css`) ), testInlineExpectsErrors.diagnosticsLocationMismatch( `transform`, - STImport.diagnostics.UNKNOWN_IMPORTED_FILE(`./unknown.st.css`), + stImportDiagnostics.UNKNOWN_IMPORTED_FILE(`./unknown.st.css`), `(label): ` ), ]) @@ -1058,12 +1065,12 @@ describe('inline-expectations', () => { '/style.st.css': { namespace: 'entry', content: ` - /* @transform-warn word(something-else) ${transformerWarnings.UNKNOWN_PSEUDO_ELEMENT( + /* @transform-warn word(something-else) ${transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT( `not-a-real-thing` )} */ .root::not-a-real-thing {} - /* @transform-warn(many) word(a b c) ${transformerWarnings.UNKNOWN_PSEUDO_ELEMENT( + /* @transform-warn(many) word(a b c) ${transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT( `not-a-real-thing` )} */ .root::not-a-real-thing {} @@ -1077,12 +1084,12 @@ describe('inline-expectations', () => { testInlineExpectsErrors.diagnosticsWordMismatch( `transform`, `something-else`, - transformerWarnings.UNKNOWN_PSEUDO_ELEMENT(`not-a-real-thing`) + transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT(`not-a-real-thing`) ), testInlineExpectsErrors.diagnosticsWordMismatch( `transform`, `a b c`, - transformerWarnings.UNKNOWN_PSEUDO_ELEMENT(`not-a-real-thing`), + transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT(`not-a-real-thing`), `(many): ` ), ]) @@ -1095,12 +1102,12 @@ describe('inline-expectations', () => { '/style.st.css': { namespace: 'entry', content: ` - /* @transform-info ${transformerWarnings.UNKNOWN_PSEUDO_ELEMENT( + /* @transform-info ${transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT( `not-a-real-thing` )} */ .root::not-a-real-thing {} - /* @transform-error(label) ${transformerWarnings.UNKNOWN_PSEUDO_ELEMENT( + /* @transform-warning(label) ${transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT( `not-a-real-thing` )} */ .root::not-a-real-thing {} @@ -1114,14 +1121,14 @@ describe('inline-expectations', () => { testInlineExpectsErrors.diagnosticsSeverityMismatch( `transform`, `info`, - `warning`, - transformerWarnings.UNKNOWN_PSEUDO_ELEMENT(`not-a-real-thing`) + `error`, + transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT(`not-a-real-thing`) ), testInlineExpectsErrors.diagnosticsSeverityMismatch( `transform`, - `error`, `warning`, - transformerWarnings.UNKNOWN_PSEUDO_ELEMENT(`not-a-real-thing`), + `error`, + transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT(`not-a-real-thing`), `(label): ` ), ]) @@ -1165,12 +1172,12 @@ describe('inline-expectations', () => { '/style.st.css': { namespace: 'entry', content: ` - /* @transform-warn(should match) word(./x.st.css) ${STImport.diagnostics.UNKNOWN_IMPORTED_FILE( + /* @transform-error(should match) word(./x.st.css) ${stImportDiagnostics.UNKNOWN_IMPORTED_FILE( `./x.st.css` )} */ @st-import A from "./x.st.css"; - /* @transform-warn(should fail) ${STImport.diagnostics.ST_IMPORT_EMPTY_FROM()} */ + /* @transform-error(should fail) ${stImportDiagnostics.ST_IMPORT_EMPTY_FROM()} */ @st-import B from "./x.st.css"; `, }, @@ -1181,7 +1188,7 @@ describe('inline-expectations', () => { expect(() => testInlineExpects(result)).to.throw( testInlineExpectsErrors.diagnosticExpectedNotFound( `transform`, - STImport.diagnostics.ST_IMPORT_EMPTY_FROM() + stImportDiagnostics.ST_IMPORT_EMPTY_FROM() ) ); }); diff --git a/packages/core-test-kit/test/test-stylable-core.spec.ts b/packages/core-test-kit/test/test-stylable-core.spec.ts index 8157f0ecc..92095a80c 100644 --- a/packages/core-test-kit/test/test-stylable-core.spec.ts +++ b/packages/core-test-kit/test/test-stylable-core.spec.ts @@ -43,7 +43,7 @@ describe(`testStylableCore()`, () => { const newMeta = stylable.analyze(`/new.st.css`); stylable.transform(newMeta); - expect(newMeta.outputAst?.toString().trim()).to.equal(`.entry__part {}`); + expect(newMeta.targetAst?.toString().trim()).to.equal(`.entry__part {}`); }); describe(`multiple files`, () => { it(`should accept a multiple files (transform all by default)`, () => { diff --git a/packages/core/package.json b/packages/core/package.json index aa6036207..4dfde5bca 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@stylable/core", - "version": "4.14.0", + "version": "5.0.0", "description": "CSS for Components", "main": "dist/index.js", "scripts": { @@ -34,7 +34,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/core/src/custom-values.ts b/packages/core/src/custom-values.ts index 1bac63279..dc36d2810 100644 --- a/packages/core/src/custom-values.ts +++ b/packages/core/src/custom-values.ts @@ -247,7 +247,7 @@ function getFlatValue( return parts.map((v) => getBoxValue([], v, fallbackNode, customTypes)).join(delimiter); } -export function getBoxValue( +function getBoxValue( path: string[], value: string | Box, node: ParsedValue, diff --git a/packages/core/src/deprecated/deprecated-selector-utils.ts b/packages/core/src/deprecated/deprecated-selector-utils.ts deleted file mode 100644 index 87a1e749a..000000000 --- a/packages/core/src/deprecated/deprecated-selector-utils.ts +++ /dev/null @@ -1,312 +0,0 @@ -import cssSelectorTokenizer from 'css-selector-tokenizer'; - -/**@deprecated*/ -export interface SelectorAstNode { - type: string; - name: string; - nodes: SelectorAstNode[]; - content?: string; - before?: string; - value?: string; - operator?: string; -} - -/**@deprecated*/ -export interface PseudoSelectorAstNode extends SelectorAstNode { - type: 'pseudo-class'; - content: string; -} - -type nodeWithPseudo = Partial & { pseudo: Array> }; - -export function parseSelector(selector: string): SelectorAstNode { - return cssSelectorTokenizer.parse(selector) as SelectorAstNode; -} - -export function stringifySelector(ast: SelectorAstNode): string { - return cssSelectorTokenizer.stringify(ast as cssSelectorTokenizer.SelectorsNode); -} - -export function isNodeMatch(nodeA: Partial, nodeB: Partial) { - return nodeA.type === nodeB.type && nodeA.name === nodeB.name; -} - -export function filterChunkNodesByType( - chunk: SelectorChunk, - typeOptions: string[] -): Array> { - return chunk.nodes.filter((node) => { - return node.type && typeOptions.includes(node.type); - }); -} - -export function fixChunkOrdering(selectorNode: SelectorAstNode, prefixType: SelectorAstNode) { - let startChunkIndex = 0; - let moved = false; - traverseNode(selectorNode, (node, index, nodes) => { - if (node.type === 'operator' || node.type === 'spacing') { - startChunkIndex = index + 1; - moved = false; - } else if (isNodeMatch(node, prefixType)) { - if (index > 0 && !moved) { - moved = true; - nodes.splice(index, 1); - nodes.splice(startChunkIndex, 0, node); - } - // return false; - } - return undefined; - }); -} - -/**@deprecated*/ -export interface SelectorChunk { - type: string; - operator?: string; - value?: string; - nodes: Array>; -} -export function separateChunks(selectorNode: SelectorAstNode) { - const selectors: SelectorChunk[][] = []; - - traverseNode(selectorNode, (node) => { - if (node.type === 'selectors') { - // skip - } else if (node.type === 'selector') { - selectors.push([{ type: 'selector', nodes: [] }]); - } else if (node.type === 'operator') { - const chunks = selectors[selectors.length - 1]; - chunks.push({ type: node.type, operator: node.operator, nodes: [] }); - } else if (node.type === 'spacing') { - const chunks = selectors[selectors.length - 1]; - chunks.push({ type: node.type, value: node.value, nodes: [] }); - } else { - const chunks = selectors[selectors.length - 1]; - chunks[chunks.length - 1].nodes.push(node); - } - }); - return selectors; -} - -/**@deprecated*/ -export interface SelectorChunk2 { - type: string; - operator?: string; - value?: string; - nodes: SelectorAstNode[]; - before?: string; -} -export function separateChunks2(selectorNode: SelectorAstNode) { - const selectors: SelectorChunk2[][] = []; - selectorNode.nodes.map(({ nodes, before }) => { - selectors.push([{ type: 'selector', nodes: [], before }]); - nodes.forEach((node) => { - if (node.type === 'operator') { - const chunks = selectors[selectors.length - 1]; - chunks.push({ ...node, nodes: [] }); - } else if (node.type === 'spacing') { - const chunks = selectors[selectors.length - 1]; - chunks.push({ ...node, nodes: [] }); - } else { - const chunks = selectors[selectors.length - 1]; - chunks[chunks.length - 1].nodes.push(node); - } - }); - }); - return selectors; -} -export function mergeChunks(chunks: SelectorChunk2[][]) { - const ast: any = { type: 'selectors', nodes: [] }; - let i = 0; - - for (const selectorChunks of chunks) { - ast.nodes[i] = { type: 'selector', nodes: [] }; - for (const chunk of selectorChunks) { - if (chunk.type !== 'selector') { - ast.nodes[i].nodes.push(chunk); - } else { - ast.nodes[i].before = chunk.before; - } - for (const node of chunk.nodes) { - ast.nodes[i].nodes.push(node); - } - } - i++; - } - return ast; -} - -export function isNested(parentChain: SelectorAstNode[]) { - let i = parentChain.length; - while (i--) { - if (parentChain[i].type === 'nested-pseudo-class') { - return true; - } - } - return false; -} - -/**@deprecated*/ -export type Visitor = ( - node: SelectorAstNode, - index: number, - nodes: SelectorAstNode[], - parents: SelectorAstNode[] -) => boolean | void; -export function traverseNode( - node: SelectorAstNode, - visitor: Visitor, - index = 0, - nodes: SelectorAstNode[] = [node], - parents: SelectorAstNode[] = [] -): boolean | void { - if (!node) { - return; - } - const cNodes = node.nodes; - let doNext = visitor(node, index, nodes, parents); - if (doNext === false) { - return false; - } - if (doNext === true) { - return true; - } - if (cNodes) { - parents = [...parents, node]; - for (let i = 0; i < node.nodes.length; i++) { - doNext = traverseNode(node.nodes[i], visitor, i, node.nodes, parents); - if (doNext === false) { - return false; - } - } - } -} - -export function isGlobal(node: SelectorAstNode) { - return node.type === 'nested-pseudo-class' && node.name === 'global'; -} - -export function createChecker(types: Array) { - return () => { - let index = 0; - return (node: SelectorAstNode) => { - const matcher = types[index]; - if (Array.isArray(matcher)) { - return matcher.includes(node.type); - } else if (matcher !== node.type) { - return false; - } - if (types[index] !== node.type) { - return false; - } - index++; - return true; - }; - }; -} -export const createSimpleSelectorChecker = createChecker([ - 'selectors', - 'selector', - ['element', 'class'], -]); -export function isSimpleSelector(selectorAst: SelectorAstNode) { - const isSimpleSelectorASTNode = createSimpleSelectorChecker(); - const isSimple = traverseNode( - selectorAst, - (node) => isSimpleSelectorASTNode(node) !== false /*stop on complex selector */ - ); - - return isSimple; -} - -export function isImport(ast: SelectorAstNode): boolean { - const selectors = ast.nodes[0]; - const selector = selectors && selectors.nodes[0]; - return selector && selector.type === 'pseudo-class' && selector.name === 'import'; -} - -export function matchAtMedia(selector: string) { - return selector.match(/^@media\s*(.*)/); -} - -export function matchAtKeyframes(selector: string) { - return selector.match(/^@keyframes\s*(.*)/); -} - -export function matchSelectorTarget(sourceSelector: string, targetSelector: string): boolean { - const a = separateChunks(parseSelector(sourceSelector)); - const b = separateChunks(parseSelector(targetSelector)); - - if (a.length > 1) { - throw new Error('source selector must not be composed of more than one compound selector'); - } - const lastChunkA = getLastChunk(a[0]); - const relevantChunksA = groupClassesAndPseudoElements( - filterChunkNodesByType(lastChunkA, ['class', 'element', 'pseudo-element']) - ); - - return b.some((compoundSelector) => { - const lastChunkB = getLastChunk(compoundSelector); - let relevantChunksB = groupClassesAndPseudoElements( - filterChunkNodesByType(lastChunkB, ['class', 'element', 'pseudo-element']) - ); - - relevantChunksB = relevantChunksB.filter((nodeB) => - relevantChunksA.find((nodeA) => isNodeMatch(nodeA, nodeB)) - ); - return containsInTheEnd(relevantChunksA, relevantChunksB); - }); -} - -const containsInTheEnd = ( - originalElements: nodeWithPseudo[], - currentMatchingElements: nodeWithPseudo[] -) => { - const offset = originalElements.length - currentMatchingElements.length; - let arraysEqual = false; - if (offset >= 0 && currentMatchingElements.length > 0) { - arraysEqual = true; - for (let i = 0; i < currentMatchingElements.length; i++) { - const a = originalElements[i + offset]; - const b = currentMatchingElements[i]; - if (a.name !== b.name || a.type !== b.type || !isPseudoDiff(a, b)) { - arraysEqual = false; - break; - } - } - } - return arraysEqual; -}; -function isPseudoDiff(a: nodeWithPseudo, b: nodeWithPseudo) { - const aNodes = a.pseudo; - const bNodes = b.pseudo; - - if (!aNodes || !bNodes || aNodes.length !== bNodes.length) { - return false; - } - return aNodes.every((node, index) => isNodeMatch(node, bNodes[index])); -} -function getLastChunk(selectorChunk: SelectorChunk[]): SelectorChunk { - return selectorChunk[selectorChunk.length - 1]; -} -function groupClassesAndPseudoElements(nodes: Array>): nodeWithPseudo[] { - const nodesWithPseudos: nodeWithPseudo[] = []; - nodes.forEach((node) => { - if (node.type === 'class' || node.type === 'element') { - nodesWithPseudos.push({ ...node, pseudo: [] }); - } else if (node.type === 'pseudo-element') { - nodesWithPseudos[nodesWithPseudos.length - 1].pseudo.push({ ...node }); - } - }); - - const nodesNoDuplicates: nodeWithPseudo[] = []; - nodesWithPseudos.forEach((node) => { - if ( - node.pseudo.length || - !nodesWithPseudos.find((n) => isNodeMatch(n, node) && node !== n) - ) { - nodesNoDuplicates.push(node); - } - }); - return nodesNoDuplicates; -} diff --git a/packages/core/src/deprecated/deprecated-stylable-utils.ts b/packages/core/src/deprecated/deprecated-stylable-utils.ts deleted file mode 100644 index b8a4702b3..000000000 --- a/packages/core/src/deprecated/deprecated-stylable-utils.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { - SelectorAstNode, - parseSelector, - traverseNode, - stringifySelector, - fixChunkOrdering, - isNodeMatch, -} from './deprecated-selector-utils'; -import type { SRule } from './postcss-ast-extension'; -import { STSymbol, Imported } from '../features'; -import type { StylableMeta } from '../stylable-meta'; -import cloneDeep from 'lodash.clonedeep'; -import * as postcss from 'postcss'; - -/** new version scopeNestedSelector at selector.ts */ -export function scopeSelector( - scopeSelectorRule: string, - targetSelectorRule: string, - rootScopeLevel = false -): { selector: string; selectorAst: SelectorAstNode } { - const scopingSelectorAst = parseSelector(scopeSelectorRule); - const targetSelectorAst = parseSelector(targetSelectorRule); - - const nodes: any[] = []; - targetSelectorAst.nodes.forEach((targetSelector) => { - scopingSelectorAst.nodes.forEach((scopingSelector) => { - const outputSelector: any = cloneDeep(targetSelector); - - outputSelector.before = scopingSelector.before || outputSelector.before; - - const first = outputSelector.nodes[0]; - const parentRef = first.type === 'invalid' && first.value === '&'; - const globalSelector = first.type === 'nested-pseudo-class' && first.name === 'global'; - - const startsWithScoping = rootScopeLevel - ? scopingSelector.nodes.every((node: any, i) => { - const o = outputSelector.nodes[i]; - for (const k in node) { - if (node[k] !== o[k]) { - return false; - } - } - return true; - }) - : false; - - if ( - first && - first.type !== 'spacing' && - !parentRef && - !startsWithScoping && - !globalSelector - ) { - outputSelector.nodes.unshift(...cloneDeep(scopingSelector.nodes), { - type: 'spacing', - value: ' ', - }); - } - - traverseNode(outputSelector, (node, i, nodes) => { - if (node.type === 'invalid' && node.value === '&') { - nodes.splice(i, 1, ...cloneDeep(scopingSelector.nodes)); - } - }); - - nodes.push(outputSelector); - }); - }); - - scopingSelectorAst.nodes = nodes; - - return { - selector: stringifySelector(scopingSelectorAst), - selectorAst: scopingSelectorAst, - }; -} - -/** new version createSubsetAst at rule.ts */ -export function createSubsetAst( - root: postcss.Root | postcss.AtRule, - selectorPrefix: string, - mixinTarget?: T, - isRoot = false -): T { - // keyframes on class mixin? - - const prefixType = parseSelector(selectorPrefix).nodes[0].nodes[0]; - const containsPrefix = containsMatchInFirstChunk.bind(null, prefixType); - const mixinRoot = mixinTarget ? mixinTarget : postcss.root(); - - root.nodes.forEach((node) => { - if (node.type === 'rule') { - const ast = isRoot - ? scopeSelector(selectorPrefix, node.selector, true).selectorAst - : parseSelector(node.selector); - - const matchesSelectors = isRoot - ? ast.nodes - : ast.nodes.filter((node) => containsPrefix(node)); - - if (matchesSelectors.length) { - const selector = stringifySelector({ - ...ast, - nodes: matchesSelectors.map((selectorNode) => { - if (!isRoot) { - fixChunkOrdering(selectorNode, prefixType); - } - - return destructiveReplaceNode(selectorNode, prefixType, { - type: 'invalid', - value: '&', - } as SelectorAstNode); - }), - }); - - mixinRoot.append(node.clone({ selector })); - } - } else if (node.type === 'atrule') { - if (node.name === 'media' || node.name === 'supports') { - const atRuleSubset = createSubsetAst( - node, - selectorPrefix, - postcss.atRule({ - params: node.params, - name: node.name, - }), - isRoot - ); - if (atRuleSubset.nodes) { - mixinRoot.append(atRuleSubset); - } - } else if (isRoot) { - mixinRoot.append(node.clone()); - } - } else { - // TODO: add warn? - } - }); - - return mixinRoot as T; -} -function destructiveReplaceNode( - ast: SelectorAstNode, - matchNode: SelectorAstNode, - replacementNode: SelectorAstNode -) { - traverseNode(ast, (node) => { - if (isNodeMatch(node, matchNode)) { - node.type = 'selector'; - node.nodes = [replacementNode]; - } - }); - return ast; -} -function containsMatchInFirstChunk(prefixType: SelectorAstNode, selectorNode: SelectorAstNode) { - let isMatch = false; - traverseNode(selectorNode, (node) => { - if (node.type === 'operator' || node.type === 'spacing') { - return false; - } else if (node.type === 'nested-pseudo-class') { - return true; - } else if (isNodeMatch(node, prefixType)) { - isMatch = true; - return false; - } - return undefined; - }); - return isMatch; -} - -export function removeUnusedRules( - ast: postcss.Root, - meta: StylableMeta, - _import: Imported, - usedFiles: string[], - resolvePath: (ctx: string, path: string) => string -): void { - const isUnusedImport = !usedFiles.includes(_import.from); - - if (isUnusedImport) { - const symbols = Object.keys(_import.named).concat(_import.defaultExport); // .filter(Boolean); - ast.walkRules((rule) => { - let shouldOutput = true; - traverseNode((rule as SRule).selectorAst, (node) => { - // TODO: remove. - if (symbols.includes(node.name)) { - return (shouldOutput = false); - } - const symbol = STSymbol.get(meta, node.name); - if (symbol && (symbol._kind === 'class' || symbol._kind === 'element')) { - let extend = symbol[`-st-extends`] || symbol.alias; - extend = extend && extend._kind !== 'import' ? extend.alias || extend : extend; - - if ( - extend && - extend._kind === 'import' && - !usedFiles.includes(resolvePath(meta.source, extend.import.from)) - ) { - return (shouldOutput = false); - } - } - return undefined; - }); - // TODO: optimize the multiple selectors - if (!shouldOutput && (rule as SRule).selectorAst.nodes.length <= 1) { - rule.remove(); - } - }); - } -} - -export function findRule( - root: postcss.Root, - selector: string, - test: any = (statement: any) => statement.prop === `-st-extends` -): null | postcss.Declaration { - let found: any = null; - root.walkRules(selector, (rule) => { - const declarationIndex = rule.nodes ? rule.nodes.findIndex(test) : -1; - if ((rule as SRule).isSimpleSelector && !!~declarationIndex) { - found = rule.nodes[declarationIndex]; - } - }); - return found; -} diff --git a/packages/core/src/deprecated/leftovers.ts b/packages/core/src/deprecated/leftovers.ts deleted file mode 100644 index b7d94e572..000000000 --- a/packages/core/src/deprecated/leftovers.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { MappedStates } from '../features'; - -/**@deprecated */ -export interface TypedClass { - '-st-root'?: boolean; - '-st-states'?: string[] | MappedStates; - '-st-extends'?: string; -} diff --git a/packages/core/src/deprecated/memory-minimal-fs.ts b/packages/core/src/deprecated/memory-minimal-fs.ts deleted file mode 100644 index 838b65ba9..000000000 --- a/packages/core/src/deprecated/memory-minimal-fs.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { dirname } from 'path'; -import deindent from 'deindent'; -import type { MinimalFS } from '../cached-process-file'; - -export interface File { - content: string; - mtime?: Date; -} -export interface MinimalFSSetup { - files: { [absolutePath: string]: File }; - trimWS?: boolean; -} - -export function createMinimalFS({ files, trimWS }: MinimalFSSetup) { - const creationDate = new Date(); - const filePaths = new Map( - Object.entries(files).map(([filePath, { content, mtime = creationDate }]) => [ - filePath, - { content, mtime }, - ]) - ); - const directoryPaths = new Set(); - for (const filePath of filePaths.keys()) { - for (const directoryPath of getParentPaths(dirname(filePath))) { - directoryPaths.add(directoryPath); - } - } - - const fs: MinimalFS = { - readFileSync(path: string) { - if (!files[path]) { - throw new Error('Cannot find file: ' + path); - } - if (trimWS) { - return deindent(files[path].content).trim(); - } - return files[path].content; - }, - statSync(path: string) { - const isDirectory = directoryPaths.has(path); - const fileEntry = filePaths.get(path); - - if (!fileEntry && !isDirectory) { - throw new Error(`ENOENT: no such file or directory, stat ${path}`); - } - - return { - isDirectory() { - return isDirectory; - }, - isFile() { - return !!fileEntry; - }, - mtime: fileEntry ? fileEntry.mtime : new Date(), - }; - }, - readlinkSync() { - throw new Error(`not implemented`); - }, - }; - - const requireModule = function require(id: string): any { - const _module = { - id, - exports: {}, - }; - try { - if (!id.match(/\.js$/)) { - id += '.js'; - } - // eslint-disable-next-line @typescript-eslint/no-implied-eval - const fn = new Function('module', 'exports', 'require', files[id].content); - fn(_module, _module.exports, requireModule); - } catch (e) { - throw new Error('Cannot require file: ' + id); - } - return _module.exports; - }; - - function resolvePath(_ctx: string, path: string) { - return path; - } - - return { - fs, - requireModule, - resolvePath, - }; -} - -function getParentPaths(initialDirectoryPath: string) { - const parentPaths: string[] = []; - - let currentPath = initialDirectoryPath; - let lastPath: string | undefined; - - while (currentPath !== lastPath) { - parentPaths.push(currentPath); - lastPath = currentPath; - currentPath = dirname(currentPath); - } - - return parentPaths; -} diff --git a/packages/core/src/deprecated/postcss-ast-extension.ts b/packages/core/src/deprecated/postcss-ast-extension.ts index e6df6ccf0..173ea68fe 100644 --- a/packages/core/src/deprecated/postcss-ast-extension.ts +++ b/packages/core/src/deprecated/postcss-ast-extension.ts @@ -1,70 +1,25 @@ -import { setFieldForDeprecation } from '../helpers/deprecation'; -import type { RefedMixin } from '../features'; -import type { SelectorAstNode } from './deprecated-selector-utils'; -import { Rule, Declaration } from 'postcss'; +import { setFieldForDeprecation, ignoreDeprecationWarn } from '../helpers/deprecation'; +import { Rule } from 'postcss'; /** * mark extended fields as deprecated. * `valueOnThis` is used because postcss.clone copies own properties. */ -setFieldForDeprecation(Rule.prototype, `selectorAst`, { - objectType: `SRule`, - valueOnThis: true, -}); -setFieldForDeprecation(Rule.prototype, `isSimpleSelector`, { - objectType: `SRule`, - valueOnThis: true, -}); -setFieldForDeprecation(Rule.prototype, `selectorType`, { - objectType: `SRule`, - valueOnThis: true, -}); -setFieldForDeprecation(Rule.prototype, `mixins`, { - objectType: `SRule`, - valueOnThis: true, -}); setFieldForDeprecation(Rule.prototype, `stScopeSelector`, { objectType: `SRule`, valueOnThis: true, pleaseUse: `getRuleScopeSelector(rule)`, }); -setFieldForDeprecation(Declaration.prototype, `stylable`, { - objectType: `SDecl`, - valueOnThis: true, - // pleaseUse, // not sure we will support an alternative without a real use case found -}); - /** * extended types */ /**@deprecated*/ export interface SRule extends Rule { - selectorAst: SelectorAstNode; - isSimpleSelector: boolean; - selectorType: 'class' | 'element' | 'complex'; - /**@deprecated*/ - mixins?: RefedMixin[]; stScopeSelector?: string; } -/**@deprecated*/ -export interface DeclStylableProps { - sourceValue: string; -} -/**@deprecated*/ -export interface SDecl extends Declaration { - stylable: DeclStylableProps; -} - -/** - * helpers? - */ -export function getDeclStylable(decl: SDecl): DeclStylableProps { - if (decl.stylable) { - return decl.stylable; - } else { - decl.stylable = decl.stylable ? decl.stylable : { sourceValue: '' }; - return decl.stylable; - } +// ToDo: remove when st-scope moves to transformer +export function getRuleScopeSelector(rule: Rule) { + return ignoreDeprecationWarn(() => (rule as SRule).stScopeSelector); } diff --git a/packages/core/src/deprecated/value-mapping.ts b/packages/core/src/deprecated/value-mapping.ts deleted file mode 100644 index 0831d7b68..000000000 --- a/packages/core/src/deprecated/value-mapping.ts +++ /dev/null @@ -1,45 +0,0 @@ -/**@deprecated use string instead*/ -export const rootValueMapping = { - vars: ':vars' as const, - import: ':import' as const, - stScope: 'st-scope' as const, - namespace: 'namespace' as const, -}; -/**@deprecated use string instead*/ -export const valueMapping = { - from: '-st-from' as const, - named: '-st-named' as const, - default: '-st-default' as const, - root: '-st-root' as const, - states: '-st-states' as const, - extends: '-st-extends' as const, - mixin: '-st-mixin' as const, // ToDo: change to STMixin.MixinType.ALL, - partialMixin: '-st-partial-mixin' as const, // ToDo: change to STMixin.MixinType.PARTIAL, - global: '-st-global' as const, -}; - -/**@deprecated */ -export type stKeys = keyof typeof valueMapping; - -/**@deprecated */ -export const stValues: string[] = Object.keys(valueMapping).map( - (key) => valueMapping[key as stKeys] -); - -/**@deprecated */ -export const stValuesMap: Record = Object.keys(valueMapping).reduce((acc, key) => { - acc[valueMapping[key as stKeys]] = true; - return acc; -}, {} as Record); - -/**@deprecated */ -export const STYLABLE_NAMED_MATCHER = new RegExp(`^${valueMapping.named}-(.+)`); - -/**@deprecated */ -export const mixinDeclRegExp = new RegExp(`(${valueMapping.mixin})|(${valueMapping.partialMixin})`); - -/**@deprecated */ -export const animationPropRegExp = /animation$|animation-name$/; - -/**@deprecated */ -export const STYLABLE_VALUE_MATCHER = /^-st-/; diff --git a/packages/core/src/diagnostics.ts b/packages/core/src/diagnostics.ts index dcf93216e..f0d0788c0 100644 --- a/packages/core/src/diagnostics.ts +++ b/packages/core/src/diagnostics.ts @@ -1,35 +1,48 @@ import type * as postcss from 'postcss'; -export type DiagnosticType = 'error' | 'warning' | 'info'; +export type DiagnosticSeverity = 'error' | 'warning' | 'info'; -export interface DiagnosticOptions { - word?: string; +export interface DiagnosticBase { + severity: DiagnosticSeverity; + message: string; + code: string; } -export interface Diagnostic { - type: DiagnosticType; +export interface DiagnosticContext { node: postcss.Node; - message: string; - options: DiagnosticOptions; + word?: string; + filePath?: string; +} + +export interface DiagnosticOptions { + word?: string; } +export type Diagnostic = DiagnosticBase & DiagnosticContext; + export class Diagnostics { constructor(public reports: Diagnostic[] = []) {} - public add( - type: DiagnosticType, - node: postcss.Node, - message: string, - options: DiagnosticOptions = {} - ) { - this.reports.push({ type, node, message, options }); - } - public error(node: postcss.Node, message: string, options?: DiagnosticOptions) { - this.add('error', node, message, options); - } - public warn(node: postcss.Node, message: string, options?: DiagnosticOptions) { - this.add('warning', node, message, options); - } - public info(node: postcss.Node, message: string, options?: DiagnosticOptions) { - this.add('info', node, message, options); + public report(diagnostic: DiagnosticBase, context: DiagnosticContext) { + const node = context.node; + this.reports.push({ + filePath: node.source?.input.from, + ...diagnostic, + ...context, + }); } } + +export function createDiagnosticReporter( + code: string, + severity: DiagnosticSeverity, + message: (...args: T) => string +) { + const func = (...args: T): DiagnosticBase => { + return { code, severity, message: message(...args) }; + }; + + func.code = code; + func.severity = severity; + + return func; +} diff --git a/packages/core/src/features/css-class.ts b/packages/core/src/features/css-class.ts index d1555ca4d..46d3e1a69 100644 --- a/packages/core/src/features/css-class.ts +++ b/packages/core/src/features/css-class.ts @@ -11,14 +11,16 @@ import { namespaceEscape, unescapeCSS } from '../helpers/escape'; import { convertToSelector, convertToClass, stringifySelector } from '../helpers/selector'; import type { StylableMeta } from '../stylable-meta'; import { validateRuleStateDefinition } from '../helpers/custom-state'; -import { ignoreDeprecationWarn } from '../helpers/deprecation'; -import type { +import type { Stylable } from '../stylable'; +import { ImmutableClass, Class, SelectorNode, ImmutableSelectorNode, + stringifySelectorAst, } from '@tokey/css-selector-parser'; import type * as postcss from 'postcss'; +import { createDiagnosticReporter } from '../diagnostics'; export interface ClassSymbol extends StylableDirectives { _kind: 'class'; @@ -29,28 +31,42 @@ export interface ClassSymbol extends StylableDirectives { export const diagnostics = { INVALID_FUNCTIONAL_SELECTOR: generalDiagnostics.INVALID_FUNCTIONAL_SELECTOR, - UNSCOPED_CLASS(name: string) { - return `unscoped class "${name}" will affect all elements of the same type in the document`; - }, - EMPTY_ST_GLOBAL() { - return `-st-global must contain a valid selector`; - }, - UNSUPPORTED_MULTI_SELECTORS_ST_GLOBAL() { - return `unsupported multi selector in -st-global`; - }, - // -st-extends - IMPORT_ISNT_EXTENDABLE() { - return 'import is not extendable'; - }, - CANNOT_EXTEND_UNKNOWN_SYMBOL(name: string) { - return `cannot extend unknown symbol "${name}"`; - }, - CANNOT_EXTEND_JS() { - return 'JS import is not extendable'; - }, - UNKNOWN_IMPORT_ALIAS(name: string) { - return `cannot use alias for unknown import "${name}"`; - }, + UNSCOPED_CLASS: createDiagnosticReporter( + '00002', + 'warning', + (name: string) => + `unscoped class "${name}" will affect all elements of the same type in the document` + ), + EMPTY_ST_GLOBAL: createDiagnosticReporter( + '00003', + 'error', + () => `-st-global must contain a valid selector` + ), + UNSUPPORTED_MULTI_SELECTORS_ST_GLOBAL: createDiagnosticReporter( + '00004', + 'error', + () => `unsupported multi selector in -st-global` + ), + IMPORT_ISNT_EXTENDABLE: createDiagnosticReporter( + '00005', + 'error', + () => 'import is not extendable' + ), + CANNOT_EXTEND_UNKNOWN_SYMBOL: createDiagnosticReporter( + '00006', + 'error', + (name: string) => `cannot extend unknown symbol "${name}"` + ), + CANNOT_EXTEND_JS: createDiagnosticReporter( + '00007', + 'error', + () => 'JS import is not extendable' + ), + UNKNOWN_IMPORT_ALIAS: createDiagnosticReporter( + '00008', + 'error', + (name: string) => `cannot use alias for unknown import "${name}"` + ), }; // HOOKS @@ -63,10 +79,10 @@ export const hooks = createFeature<{ analyzeSelectorNode({ context, node, rule }): void { if (node.nodes) { // error on functional class - context.diagnostics.error( - rule, + context.diagnostics.report( diagnostics.INVALID_FUNCTIONAL_SELECTOR(`.` + node.value, `class`), { + node: rule, word: stringifySelector(node), } ); @@ -149,6 +165,31 @@ export const hooks = createFeature<{ // API +export class StylablePublicApi { + constructor(private stylable: Stylable) {} + public transformIntoSelector(meta: StylableMeta, name: string): string | undefined { + const localSymbol = STSymbol.get(meta, name); + const resolved = + localSymbol?._kind === 'import' + ? this.stylable.resolver.deepResolve(localSymbol) + : { _kind: 'css', meta, symbol: localSymbol }; + + if (resolved?._kind !== 'css' || resolved.symbol?._kind !== 'class') { + return undefined; + } + + const node: Class = { + type: 'class', + value: '', + start: 0, + end: 0, + dotComments: [], + }; + namespaceClass(resolved.meta, resolved.symbol, node, meta); + return stringifySelectorAst(node); + } +} + export function get(meta: StylableMeta, name: string): ClassSymbol | undefined { return STSymbol.get(meta, name, `class`); } @@ -173,10 +214,6 @@ export function addClass(context: FeatureContext, name: string, rule?: postcss.R node: rule, safeRedeclare: !!alias, }); - // deprecated - ignoreDeprecationWarn(() => { - context.meta.classes[name] = STSymbol.get(context.meta, name, `class`)!; - }); } return STSymbol.get(context.meta, name, `class`)!; } @@ -224,7 +261,8 @@ export function validateClassScoping({ } else if (locallyScoped === false) { if (checkForScopedNodeAfter(context, rule, nodes, index) === false) { if (reportUnscoped) { - context.diagnostics.warn(rule, diagnostics.UNSCOPED_CLASS(node.value), { + context.diagnostics.report(diagnostics.UNSCOPED_CLASS(node.value), { + node: rule, word: node.value, }); } diff --git a/packages/core/src/features/css-custom-property.ts b/packages/core/src/features/css-custom-property.ts index d296ffd76..f555b9cdb 100644 --- a/packages/core/src/features/css-custom-property.ts +++ b/packages/core/src/features/css-custom-property.ts @@ -7,10 +7,10 @@ import { generateScopedCSSVar, atPropertyValidationWarnings, } from '../helpers/css-custom-property'; -import { ignoreDeprecationWarn } from '../helpers/deprecation'; import { validateAllowedNodesUntil, stringifyFunction } from '../helpers/value'; import { globalValue, GLOBAL_FUNC } from '../helpers/global'; import { plugableRecord } from '../helpers/plugable-record'; +import { createDiagnosticReporter } from '../diagnostics'; import type { StylableMeta } from '../stylable-meta'; import type { StylableResolver, CSSResolve } from '../stylable-resolver'; import type * as postcss from 'postcss'; @@ -25,24 +25,41 @@ export interface CSSVarSymbol { export const diagnostics = { ...atPropertyValidationWarnings, - ILLEGAL_CSS_VAR_USE(name: string) { - return `a custom css property must begin with "--" (double-dash), but received "${name}"`; - }, - ILLEGAL_CSS_VAR_ARGS(name: string) { - return `custom property "${name}" usage (var()) must receive comma separated values`; - }, - DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY() { - return `"st-global-custom-property" is deprecated and will be removed in the next version. Use "@property" with ${GLOBAL_FUNC}`; - }, - GLOBAL_CSS_VAR_MISSING_COMMA(name: string) { - return `"@st-global-custom-property" received the value "${name}", but its values must be comma separated`; - }, - ILLEGAL_GLOBAL_CSS_VAR(name: string) { - return `"@st-global-custom-property" received the value "${name}", but it must begin with "--" (double-dash)`; - }, - MISSING_PROP_NAME() { - return `missing custom property name for "var(--[PROP NAME])"`; - }, + ILLEGAL_CSS_VAR_USE: createDiagnosticReporter( + '01005', + 'error', + (name: string) => + `a custom css property must begin with "--" (double-dash), but received "${name}"` + ), + ILLEGAL_CSS_VAR_ARGS: createDiagnosticReporter( + '01006', + 'error', + (name: string) => + `custom property "${name}" usage (var()) must receive comma separated values` + ), + DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY: createDiagnosticReporter( + '01007', + 'info', + () => + `"st-global-custom-property" is deprecated and will be removed in the next version. Use "@property" with ${GLOBAL_FUNC}` + ), + GLOBAL_CSS_VAR_MISSING_COMMA: createDiagnosticReporter( + '01008', + 'error', + (name: string) => + `"@st-global-custom-property" received the value "${name}", but its values must be comma separated` + ), + ILLEGAL_GLOBAL_CSS_VAR: createDiagnosticReporter( + '01009', + 'error', + (name: string) => + `"@st-global-custom-property" received the value "${name}", but it must begin with "--" (double-dash)` + ), + MISSING_PROP_NAME: createDiagnosticReporter( + '01010', + 'error', + () => `missing custom property name for "var(--[PROP NAME])"` + ), }; const dataKey = plugableRecord.key<{ @@ -205,7 +222,8 @@ function addCSSProperty({ }) { // validate indent if (!validateCustomPropertyName(name)) { - context.diagnostics.warn(node, diagnostics.ILLEGAL_CSS_VAR_USE(name), { + context.diagnostics.report(diagnostics.ILLEGAL_CSS_VAR_USE(name), { + node, word: name, }); return; @@ -227,10 +245,6 @@ function addCSSProperty({ safeRedeclare: !final || !!alias, node, }); - // deprecated - ignoreDeprecationWarn( - () => (context.meta.cssVars[name] = STSymbol.get(context.meta, name, `cssVar`)!) - ); } function analyzeDeclValueVarCalls(context: FeatureContext, decl: postcss.Declaration) { @@ -239,13 +253,16 @@ function analyzeDeclValueVarCalls(context: FeatureContext, decl: postcss.Declara if (node.type === 'function' && node.value === 'var' && node.nodes) { const varName = node.nodes[0]; if (!varName) { - context.diagnostics.warn(decl, diagnostics.MISSING_PROP_NAME()); + context.diagnostics.report(diagnostics.MISSING_PROP_NAME(), { + node: decl, + }); return; } if (!validateAllowedNodesUntil(node, 1)) { const args = postcssValueParser.stringify(node.nodes); - context.diagnostics.warn(decl, diagnostics.ILLEGAL_CSS_VAR_ARGS(args), { + context.diagnostics.report(diagnostics.ILLEGAL_CSS_VAR_ARGS(args), { + node: decl, word: args, }); } @@ -263,7 +280,9 @@ function analyzeDeclValueVarCalls(context: FeatureContext, decl: postcss.Declara function analyzeDeprecatedStGlobalCustomProperty(context: FeatureContext, atRule: postcss.AtRule) { // report deprecation - context.diagnostics.info(atRule, diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY()); + context.diagnostics.report(diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY(), { + node: atRule, + }); // const cssVarsByComma = atRule.params.split(','); const cssVarsBySpacing = atRule.params @@ -272,7 +291,8 @@ function analyzeDeprecatedStGlobalCustomProperty(context: FeatureContext, atRule .filter((s) => s !== ','); if (cssVarsBySpacing.length > cssVarsByComma.length) { - context.diagnostics.warn(atRule, diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA(atRule.params), { + context.diagnostics.report(diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA(atRule.params), { + node: atRule, word: atRule.params, }); return; @@ -294,7 +314,8 @@ function analyzeDeprecatedStGlobalCustomProperty(context: FeatureContext, atRule const { stCustomGlobalProperty } = plugableRecord.getUnsafe(context.meta.data, dataKey); stCustomGlobalProperty[name] = STSymbol.get(context.meta, name, `cssVar`)!; } else { - context.diagnostics.warn(atRule, diagnostics.ILLEGAL_GLOBAL_CSS_VAR(name), { + context.diagnostics.report(diagnostics.ILLEGAL_GLOBAL_CSS_VAR(name), { + node: atRule, word: name, }); } diff --git a/packages/core/src/features/css-keyframes.ts b/packages/core/src/features/css-keyframes.ts index 0e9069ed8..b35b5f13f 100644 --- a/packages/core/src/features/css-keyframes.ts +++ b/packages/core/src/features/css-keyframes.ts @@ -4,12 +4,12 @@ import * as STImport from './st-import'; import type { Imported } from './st-import'; import type { StylableMeta } from '../stylable-meta'; import { plugableRecord } from '../helpers/plugable-record'; -import { ignoreDeprecationWarn } from '../helpers/deprecation'; import { isInConditionalGroup } from '../helpers/rule'; import { namespace } from '../helpers/namespace'; import { globalValue, GLOBAL_FUNC } from '../helpers/global'; import type * as postcss from 'postcss'; import postcssValueParser from 'postcss-value-parser'; +import { createDiagnosticReporter } from '../diagnostics'; export interface KeyframesSymbol { _kind: 'keyframes'; @@ -56,21 +56,32 @@ export const reservedKeyFrames = [ ]; export const diagnostics = { - ILLEGAL_KEYFRAMES_NESTING() { - return `illegal nested "@keyframes"`; - }, - MISSING_KEYFRAMES_NAME() { - return '"@keyframes" missing parameter'; - }, - MISSING_KEYFRAMES_NAME_INSIDE_GLOBAL() { - return `"@keyframes" missing parameter inside "${GLOBAL_FUNC}()"`; - }, - KEYFRAME_NAME_RESERVED(name: string) { - return `keyframes "${name}" is reserved`; - }, - UNKNOWN_IMPORTED_KEYFRAMES(name: string, path: string) { - return `cannot resolve imported keyframes "${name}" from stylesheet "${path}"`; - }, + ILLEGAL_KEYFRAMES_NESTING: createDiagnosticReporter( + '02001', + 'error', + () => `illegal nested "@keyframes"` + ), + MISSING_KEYFRAMES_NAME: createDiagnosticReporter( + '02002', + 'error', + () => '"@keyframes" missing parameter' + ), + MISSING_KEYFRAMES_NAME_INSIDE_GLOBAL: createDiagnosticReporter( + '02003', + 'error', + () => `"@keyframes" missing parameter inside "${GLOBAL_FUNC}()"` + ), + KEYFRAME_NAME_RESERVED: createDiagnosticReporter( + '02004', + 'error', + (name: string) => `keyframes "${name}" is reserved` + ), + UNKNOWN_IMPORTED_KEYFRAMES: createDiagnosticReporter( + '02005', + 'error', + (name: string, path: string) => + `cannot resolve imported keyframes "${name}" from stylesheet "${path}"` + ), }; const dataKey = plugableRecord.key<{ @@ -101,17 +112,15 @@ export const hooks = createFeature<{ let { params: name } = atRule; // check nesting validity if (!isInConditionalGroup(atRule, true)) { - context.diagnostics.error(atRule, diagnostics.ILLEGAL_KEYFRAMES_NESTING()); + context.diagnostics.report(diagnostics.ILLEGAL_KEYFRAMES_NESTING(), { node: atRule }); return; } // save keyframes declarations const { statements: keyframesAsts } = plugableRecord.getUnsafe(context.meta.data, dataKey); keyframesAsts.push(atRule); - // deprecated - ignoreDeprecationWarn(() => context.meta.keyframes.push(atRule)); // validate name if (!name) { - context.diagnostics.warn(atRule, diagnostics.MISSING_KEYFRAMES_NAME()); + context.diagnostics.report(diagnostics.MISSING_KEYFRAMES_NAME(), { node: atRule }); return; } // @@ -122,11 +131,14 @@ export const hooks = createFeature<{ global = true; } if (name === '') { - context.diagnostics.warn(atRule, diagnostics.MISSING_KEYFRAMES_NAME_INSIDE_GLOBAL()); + context.diagnostics.report(diagnostics.MISSING_KEYFRAMES_NAME_INSIDE_GLOBAL(), { + node: atRule, + }); return; } if (reservedKeyFrames.includes(name)) { - context.diagnostics.error(atRule, diagnostics.KEYFRAME_NAME_RESERVED(name), { + context.diagnostics.report(diagnostics.KEYFRAME_NAME_RESERVED(name), { + node: atRule, word: name, }); } @@ -147,10 +159,10 @@ export const hooks = createFeature<{ if (res) { resolved[name] = res; } else if (symbol.import) { - context.diagnostics.error( - symbol.import.rule, + context.diagnostics.report( diagnostics.UNKNOWN_IMPORTED_KEYFRAMES(symbol.name, symbol.import.request), { + node: symbol.import.rule, word: symbol.name, } ); @@ -247,10 +259,6 @@ function addKeyframes({ }, safeRedeclare, }); - // deprecated - ignoreDeprecationWarn(() => { - context.meta.mappedKeyframes[name] = STSymbol.get(context.meta, name, `keyframes`)!; - }); } function addKeyframesDeclaration( diff --git a/packages/core/src/features/css-layer.ts b/packages/core/src/features/css-layer.ts index 79ce728e5..816b30fcb 100644 --- a/packages/core/src/features/css-layer.ts +++ b/packages/core/src/features/css-layer.ts @@ -8,7 +8,7 @@ import { globalValueFromFunctionNode, GLOBAL_FUNC } from '../helpers/global'; import { CSSWideKeywords } from '../native-reserved-lists'; import valueParser from 'postcss-value-parser'; import type * as postcss from 'postcss'; -import type { Diagnostics } from '../diagnostics'; +import { createDiagnosticReporter, Diagnostics } from '../diagnostics'; export interface LayerSymbol { _kind: 'layer'; @@ -23,24 +23,37 @@ export interface ResolvedLayer { } export const diagnostics = { - MISSING_LAYER_NAME_INSIDE_GLOBAL() { - return `"@layer" missing parameter inside "${GLOBAL_FUNC}()"`; - }, - LAYER_SORT_STATEMENT_WITH_STYLE() { - return `"@layer" ordering statement cannot have a style block`; - }, - RESERVED_KEYWORD(name: string) { - return `"@layer" name cannot be reserved word "${name}"`; - }, - NOT_IDENT(name: string) { - return `"@layer" expected ident, but got "${name}"`; - }, - RECONFIGURE_IMPORTED(name: string) { - return `cannot reconfigure imported layer "${name}"`; - }, - UNKNOWN_IMPORTED_LAYER(name: string, path: string) { - return `cannot resolve imported layer "${name}" from stylesheet "${path}"`; - }, + MISSING_LAYER_NAME_INSIDE_GLOBAL: createDiagnosticReporter( + '19001', + 'warning', + () => `"@layer" missing parameter inside "${GLOBAL_FUNC}()"` + ), + LAYER_SORT_STATEMENT_WITH_STYLE: createDiagnosticReporter( + '19002', + 'error', + () => `"@layer" ordering statement cannot have a style block` + ), + RESERVED_KEYWORD: createDiagnosticReporter( + '19003', + 'error', + (name: string) => `"@layer" name cannot be reserved word "${name}"` + ), + NOT_IDENT: createDiagnosticReporter( + '19004', + 'error', + (name: string) => `"@layer" expected ident, but got "${name}"` + ), + RECONFIGURE_IMPORTED: createDiagnosticReporter( + '19005', + 'error', + (name: string) => `cannot reconfigure imported layer "${name}"` + ), + UNKNOWN_IMPORTED_LAYER: createDiagnosticReporter( + '19006', + 'error', + (name: string, path: string) => + `cannot resolve imported layer "${name}" from stylesheet "${path}"` + ), }; const dataKey = plugableRecord.key<{ @@ -85,7 +98,9 @@ export const hooks = createFeature<{ const analyzeMetaData = plugableRecord.getUnsafe(context.meta.data, dataKey); const analyzedParams = parseLayerParams(atRule.params, context.diagnostics, atRule); if (analyzedParams.multiple && atRule.nodes) { - context.diagnostics.error(atRule, diagnostics.LAYER_SORT_STATEMENT_WITH_STYLE()); + context.diagnostics.report(diagnostics.LAYER_SORT_STATEMENT_WITH_STYLE(), { + node: atRule, + }); } // cache params analyzeMetaData.analyzedParams[atRule.params] = analyzedParams; @@ -110,10 +125,10 @@ export const hooks = createFeature<{ if (res) { resolved[name] = res; } else if (symbol.import) { - context.diagnostics.error( - symbol.import.rule, + context.diagnostics.report( diagnostics.UNKNOWN_IMPORTED_LAYER(symbol.name, symbol.import.request), { + node: symbol.import.rule, word: symbol.name, } ); @@ -198,7 +213,7 @@ function parseLayerParams(params: string, report: Diagnostics, atRule: postcss.A names.push(globalName); globals[globalName] = true; } else if (globalName === '') { - report.warn(atRule, diagnostics.MISSING_LAYER_NAME_INSIDE_GLOBAL()); + report.report(diagnostics.MISSING_LAYER_NAME_INSIDE_GLOBAL(), { node: atRule }); } readyForName = false; } else if (type === 'div' && value === ',') { @@ -209,7 +224,7 @@ function parseLayerParams(params: string, report: Diagnostics, atRule: postcss.A } else { readyForName = false; const source = valueParser.stringify(node); - report.error(atRule, diagnostics.NOT_IDENT(source), { word: source }); + report.report(diagnostics.NOT_IDENT(source), { node: atRule, word: source }); } } return { @@ -328,7 +343,10 @@ function addLayer({ if (CSSWideKeywords.includes(name)) { // keep global = true; - context.diagnostics.error(ast, diagnostics.RESERVED_KEYWORD(name), { word: name }); + context.diagnostics.report(diagnostics.RESERVED_KEYWORD(name), { + node: ast, + word: name, + }); } const analyzeMetaData = plugableRecord.getUnsafe(context.meta.data, dataKey); analyzeMetaData.layerDefs[name] = ast; @@ -348,6 +366,9 @@ function addLayer({ } else if (!definedSymbol.import && global) { definedSymbol.global = true; } else if (definedSymbol.import && global) { - context.diagnostics.error(ast, diagnostics.RECONFIGURE_IMPORTED(name), { word: name }); + context.diagnostics.report(diagnostics.RECONFIGURE_IMPORTED(name), { + node: ast, + word: name, + }); } } diff --git a/packages/core/src/features/css-type.ts b/packages/core/src/features/css-type.ts index 5ea4f05fb..8357ec5a3 100644 --- a/packages/core/src/features/css-type.ts +++ b/packages/core/src/features/css-type.ts @@ -7,9 +7,9 @@ import * as CSSClass from './css-class'; import type { StylableMeta } from '../stylable-meta'; import { isCompRoot, stringifySelector } from '../helpers/selector'; import { getOriginDefinition } from '../helpers/resolve'; -import { ignoreDeprecationWarn } from '../helpers/deprecation'; import type { Type, ImmutableType, ImmutableSelectorNode } from '@tokey/css-selector-parser'; import type * as postcss from 'postcss'; +import { createDiagnosticReporter } from '../diagnostics'; export interface ElementSymbol extends StylableDirectives { _kind: 'element'; @@ -19,9 +19,12 @@ export interface ElementSymbol extends StylableDirectives { export const diagnostics = { INVALID_FUNCTIONAL_SELECTOR: generalDiagnostics.INVALID_FUNCTIONAL_SELECTOR, - UNSCOPED_TYPE_SELECTOR(name: string) { - return `unscoped type selector "${name}" will affect all elements of the same type in the document`; - }, + UNSCOPED_TYPE_SELECTOR: createDiagnosticReporter( + `03001`, + 'warning', + (name: string) => + `unscoped type selector "${name}" will affect all elements of the same type in the document` + ), }; // HOOKS @@ -30,23 +33,15 @@ export const hooks = createFeature<{ SELECTOR: Type; IMMUTABLE_SELECTOR: ImmutableType; }>({ - analyzeSelectorNode({ context, node, rule, walkContext: [_index, _nodes, parents] }): void { - /** - * intent to deprecate: currently `value(param)` can be used - * as a custom state value. Unless there is a reasonable - * use case, this should be removed. - */ - if ( - node.nodes && - (parents.length < 2 || - parents[parents.length - 2].type !== `pseudo_class` || - node.value !== `value`) - ) { + analyzeSelectorNode({ context, node, rule, walkContext: [_index, _nodes] }): void { + if (node.nodes) { // error on functional type - context.diagnostics.error( - rule, + context.diagnostics.report( diagnostics.INVALID_FUNCTIONAL_SELECTOR(node.value, `type`), - { word: stringifySelector(node) } + { + node: rule, + word: stringifySelector(node), + } ); } addType(context, node.value, rule); @@ -99,10 +94,6 @@ export function addType(context: FeatureContext, name: string, rule?: postcss.Ru node: rule, safeRedeclare: !!alias, }); - // deprecated - ignoreDeprecationWarn(() => { - context.meta.elements[name] = STSymbol.get(context.meta, name, `element`)!; - }); } return STSymbol.get(context.meta, name, `element`)!; } @@ -127,7 +118,8 @@ export function validateTypeScoping({ if (locallyScoped === false) { if (CSSClass.checkForScopedNodeAfter(context, rule, nodes, index) === false) { if (reportUnscoped) { - context.diagnostics.warn(rule, diagnostics.UNSCOPED_TYPE_SELECTOR(node.value), { + context.diagnostics.report(diagnostics.UNSCOPED_TYPE_SELECTOR(node.value), { + node: rule, word: node.value, }); } diff --git a/packages/core/src/features/diagnostics.ts b/packages/core/src/features/diagnostics.ts index b5359de7d..ab493b986 100644 --- a/packages/core/src/features/diagnostics.ts +++ b/packages/core/src/features/diagnostics.ts @@ -1,8 +1,14 @@ +import { createDiagnosticReporter } from '../diagnostics'; + export const generalDiagnostics = { - INVALID_FUNCTIONAL_SELECTOR(selector: string, type: string) { - return `"${selector}" ${type} is not functional`; - }, - FORBIDDEN_DEF_IN_COMPLEX_SELECTOR(name: string) { - return `cannot define "${name}" inside a complex selector`; - }, + INVALID_FUNCTIONAL_SELECTOR: createDiagnosticReporter( + '00001', + 'error', + (selector: string, type: string) => `"${selector}" ${type} is not functional` + ), + FORBIDDEN_DEF_IN_COMPLEX_SELECTOR: createDiagnosticReporter( + '05014', + 'error', + (name: string) => `cannot define "${name}" inside a complex selector` + ), }; diff --git a/packages/core/src/features/feature.ts b/packages/core/src/features/feature.ts index ba951b04a..3900d4d7d 100644 --- a/packages/core/src/features/feature.ts +++ b/packages/core/src/features/feature.ts @@ -46,7 +46,9 @@ export interface FeatureHooks { walkContext: SelectorNodeContext; }) => SelectorWalkReturn; analyzeDeclaration: (options: { context: FeatureContext; decl: postcss.Declaration }) => void; + analyzeDone: (context: FeatureContext) => void; prepareAST: (options: { + context: FeatureTransformContext; node: postcss.ChildNode; toRemove: Array void)>; }) => void; @@ -97,6 +99,9 @@ const defaultHooks: FeatureHooks = { analyzeDeclaration() { /**/ }, + analyzeDone() { + /**/ + }, prepareAST() { /**/ }, diff --git a/packages/core/src/features/index.ts b/packages/core/src/features/index.ts index 43e8272bf..6628ff40a 100644 --- a/packages/core/src/features/index.ts +++ b/packages/core/src/features/index.ts @@ -1,4 +1,4 @@ -export type { FeatureContext } from './feature'; +export type { FeatureContext, FeatureTransformContext } from './feature'; export * as STSymbol from './st-symbol'; export type { StylableSymbol } from './st-symbol'; @@ -13,8 +13,10 @@ export * as STScope from './st-scope'; export * as STVar from './st-var'; export type { VarSymbol, ComputedStVar, FlatComputedStVar } from './st-var'; +export * as STCustomSelector from './st-custom-selector'; + export * as STMixin from './st-mixin'; -export type { RefedMixin, MixinValue } from './st-mixin'; +export type { MixinReflection, MixinValue } from './st-mixin'; export * as CSSClass from './css-class'; export type { ClassSymbol } from './css-class'; diff --git a/packages/core/src/features/st-custom-selector.ts b/packages/core/src/features/st-custom-selector.ts new file mode 100644 index 000000000..bd0d91dfc --- /dev/null +++ b/packages/core/src/features/st-custom-selector.ts @@ -0,0 +1,132 @@ +import { plugableRecord } from '../helpers/plugable-record'; +import { createFeature } from './feature'; +import { + transformCustomSelectorMap, + transformCustomSelectors, + CustomSelectorMap, +} from '../helpers/custom-selector'; +import { parseSelectorWithCache } from '../helpers/selector'; +import * as postcss from 'postcss'; +import { SelectorList, stringifySelectorAst } from '@tokey/css-selector-parser'; +import type { StylableMeta } from '../stylable-meta'; +import { createDiagnosticReporter, Diagnostics } from '../diagnostics'; + +export const diagnostics = { + UNKNOWN_CUSTOM_SELECTOR: createDiagnosticReporter( + '18001', + 'error', + (selector: string) => `The selector '${selector}' is undefined` + ), +}; + +const dataKey = + plugableRecord.key< + Record< + string, + { selector: string; ast: SelectorList; isScoped: boolean; def: postcss.AtRule } + > + >('st-custom-selector'); + +export const CUSTOM_SELECTOR_RE = /:--[\w-]+/g; + +// HOOKS + +export const hooks = createFeature({ + metaInit({ meta }) { + plugableRecord.set(meta.data, dataKey, {}); + }, + analyzeAtRule({ context, atRule, analyzeRule }) { + const params = atRule.params.split(/\s/); + const customSelector = params.shift(); + if (customSelector && customSelector.match(CUSTOM_SELECTOR_RE)) { + const selector = atRule.params.replace(customSelector, '').trim(); + const ast = parseSelectorWithCache(selector, { clone: true }); + const isScoped = analyzeRule(postcss.rule({ selector, source: atRule.source }), { + isScoped: false, + }); + const analyzed = plugableRecord.getUnsafe(context.meta.data, dataKey); + const name = customSelector.slice(3); + analyzed[name] = { selector, ast, isScoped, def: atRule }; + } else { + // TODO: add warn there are two types one is not valid name and the other is empty name. + } + }, + analyzeDone(context) { + const analyzed = plugableRecord.getUnsafe(context.meta.data, dataKey); + const customSelectors: CustomSelectorMap = {}; + for (const [name, data] of Object.entries(analyzed)) { + customSelectors[name] = data.ast; + } + const inlined = transformCustomSelectorMap(customSelectors, (report) => { + if (report.type === 'unknown' && analyzed[report.origin]) { + const unknownSelector = `:--${report.unknown}`; + context.diagnostics.report(diagnostics.UNKNOWN_CUSTOM_SELECTOR(unknownSelector), { + node: analyzed[report.origin].def, + word: unknownSelector, + }); + } else if (report.type === 'circular') { + // ToDo: report error + } + }); + // cache inlined selector + for (const [name, ast] of Object.entries(inlined)) { + analyzed[name].ast = ast; + analyzed[name].selector = stringifySelectorAst(ast); + } + }, + prepareAST({ context, node, toRemove }) { + if (node.type === 'rule' && node.selector.match(CUSTOM_SELECTOR_RE)) { + node.selector = transformCustomSelectorInline(context.meta, node.selector, { + diagnostics: context.diagnostics, + node, + }); + } else if (node.type === 'atrule' && node.name === 'custom-selector') { + toRemove.push(node); + } + }, +}); + +// API + +export function isScoped(meta: StylableMeta, name: string) { + const analyzed = plugableRecord.getUnsafe(meta.data, dataKey); + return analyzed[name]?.isScoped; +} + +export function getCustomSelector(meta: StylableMeta, name: string): SelectorList | undefined { + const analyzed = plugableRecord.getUnsafe(meta.data, dataKey); + return analyzed[name]?.ast; +} + +export function getCustomSelectorExpended(meta: StylableMeta, name: string): string | undefined { + const analyzed = plugableRecord.getUnsafe(meta.data, dataKey); + return analyzed[name]?.selector; +} + +export function getCustomSelectorNames(meta: StylableMeta): string[] { + const analyzed = plugableRecord.getUnsafe(meta.data, dataKey); + return Object.keys(analyzed).map((name) => `:--${name}`); +} + +export function transformCustomSelectorInline( + meta: StylableMeta, + selector: string, + options: { diagnostics?: Diagnostics; node?: postcss.Node } = {} +) { + const ast = parseSelectorWithCache(selector, { clone: true }); + const analyzed = plugableRecord.getUnsafe(meta.data, dataKey); + const inlined = transformCustomSelectors( + ast, + (name) => analyzed[name]?.ast, + (report) => { + if (options.diagnostics && options.node) { + const unknownSelector = `:--${report.unknown}`; + options.diagnostics.report(diagnostics.UNKNOWN_CUSTOM_SELECTOR(unknownSelector), { + node: options.node, + word: unknownSelector, + }); + } + } + ); + return stringifySelectorAst(inlined); +} diff --git a/packages/core/src/features/st-global.ts b/packages/core/src/features/st-global.ts index 96feadc07..78c348220 100644 --- a/packages/core/src/features/st-global.ts +++ b/packages/core/src/features/st-global.ts @@ -13,13 +13,16 @@ import type { SelectorList, PseudoClass, } from '@tokey/css-selector-parser'; +import { createDiagnosticReporter } from '../diagnostics'; const dataKey = plugableRecord.key>('globals'); export const diagnostics = { - UNSUPPORTED_MULTI_SELECTOR_IN_GLOBAL() { - return `unsupported multi selector in :global()`; - }, + UNSUPPORTED_MULTI_SELECTOR_IN_GLOBAL: createDiagnosticReporter( + '04001', + 'error', + () => `unsupported multi selector in :global()` + ), }; // HOOKS @@ -33,7 +36,8 @@ export const hooks = createFeature<{ IMMUTABLE_SELECTOR: ImmutablePseudoClass }> return; } if (node.nodes && node.nodes?.length > 1) { - context.diagnostics.error(rule, diagnostics.UNSUPPORTED_MULTI_SELECTOR_IN_GLOBAL(), { + context.diagnostics.report(diagnostics.UNSUPPORTED_MULTI_SELECTOR_IN_GLOBAL(), { + node: rule, word: stringifySelector(node.nodes), }); } diff --git a/packages/core/src/features/st-import.ts b/packages/core/src/features/st-import.ts index 2afd364a7..87c6d5d54 100644 --- a/packages/core/src/features/st-import.ts +++ b/packages/core/src/features/st-import.ts @@ -2,13 +2,14 @@ import { createFeature, FeatureContext, FeatureTransformContext } from './featur import { generalDiagnostics } from './diagnostics'; import * as STSymbol from './st-symbol'; import { plugableRecord } from '../helpers/plugable-record'; -import { ignoreDeprecationWarn } from '../helpers/deprecation'; import { parseStImport, parsePseudoImport, parseImportMessages } from '../helpers/import'; import { validateCustomPropertyName } from '../helpers/css-custom-property'; import type { StylableMeta } from '../stylable-meta'; import path from 'path'; import type { ImmutablePseudoClass, PseudoClass } from '@tokey/css-selector-parser'; import type * as postcss from 'postcss'; +import { createDiagnosticReporter } from '../diagnostics'; +import type { Stylable } from '../stylable'; export interface ImportSymbol { _kind: 'import'; @@ -18,6 +19,15 @@ export interface ImportSymbol { context: string; } +export interface AnalyzedImport { + from: string; + default: string; + named: Record; + typed: { + keyframes: Record; + }; +} + export interface Imported { from: string; defaultExport: string; @@ -54,25 +64,39 @@ const dataKey = plugableRecord.key('imports'); export const diagnostics = { ...parseImportMessages, - NO_ST_IMPORT_IN_NESTED_SCOPE() { - return `cannot use "@st-import" inside of nested scope`; - }, - NO_PSEUDO_IMPORT_IN_NESTED_SCOPE() { - return `cannot use ":import" inside of nested scope`; - }, - INVALID_CUSTOM_PROPERTY_AS_VALUE(name: string, as: string) { - return `invalid alias for custom property "${name}" as "${as}"; custom properties must be prefixed with "--" (double-dash)`; - }, FORBIDDEN_DEF_IN_COMPLEX_SELECTOR: generalDiagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR, - UNKNOWN_IMPORTED_SYMBOL(name: string, path: string) { - return `cannot resolve imported symbol "${name}" from stylesheet "${path}"`; - }, - UNKNOWN_IMPORTED_FILE(path: string) { - return `cannot resolve imported file: "${path}"`; - }, - UNKNOWN_TYPED_IMPORT(type: string) { - return `Unknown type import "${type}"`; - }, + NO_ST_IMPORT_IN_NESTED_SCOPE: createDiagnosticReporter( + '05011', + 'error', + () => `cannot use "@st-import" inside of nested scope` + ), + NO_PSEUDO_IMPORT_IN_NESTED_SCOPE: createDiagnosticReporter( + '05012', + 'error', + () => `cannot use ":import" inside of nested scope` + ), + INVALID_CUSTOM_PROPERTY_AS_VALUE: createDiagnosticReporter( + '05013', + 'error', + (name: string, as: string) => + `invalid alias for custom property "${name}" as "${as}"; custom properties must be prefixed with "--" (double-dash)` + ), + UNKNOWN_IMPORTED_SYMBOL: createDiagnosticReporter( + '05015', + 'error', + (name: string, path: string) => + `cannot resolve imported symbol "${name}" from stylesheet "${path}"` + ), + UNKNOWN_IMPORTED_FILE: createDiagnosticReporter( + '05016', + 'error', + (path: string) => `cannot resolve imported file: "${path}"` + ), + UNKNOWN_TYPED_IMPORT: createDiagnosticReporter( + '05018', + 'error', + (type: string) => `Unknown type import "${type}"` + ), }; // HOOKS @@ -88,7 +112,7 @@ export const hooks = createFeature<{ const imports = plugableRecord.getUnsafe(context.meta.data, dataKey); const dirContext = path.dirname(context.meta.source); // collect shallow imports - for (const node of context.meta.ast.nodes) { + for (const node of context.meta.sourceAst.nodes) { if (!isImportStatement(node)) { continue; } @@ -97,9 +121,6 @@ export const hooks = createFeature<{ ? parseStImport(node, dirContext, context.diagnostics) : parsePseudoImport(node, dirContext, context.diagnostics); imports.push(parsedImport); - ignoreDeprecationWarn(() => { - context.meta.imports.push(parsedImport); - }); addImportSymbols(parsedImport, context, dirContext); } }, @@ -108,7 +129,9 @@ export const hooks = createFeature<{ return; } if (atRule.parent?.type !== `root`) { - context.diagnostics.warn(atRule, diagnostics.NO_ST_IMPORT_IN_NESTED_SCOPE()); + context.diagnostics.report(diagnostics.NO_ST_IMPORT_IN_NESTED_SCOPE(), { + node: atRule, + }); } }, analyzeSelectorNode({ context, rule, node }) { @@ -116,14 +139,16 @@ export const hooks = createFeature<{ return; } if (rule.selector !== `:import`) { - context.diagnostics.warn( - rule, - diagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR(PseudoImport) + context.diagnostics.report( + diagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR(PseudoImport), + { node: rule } ); return; } if (rule.parent?.type !== `root`) { - context.diagnostics.warn(rule, diagnostics.NO_PSEUDO_IMPORT_IN_NESTED_SCOPE()); + context.diagnostics.report(diagnostics.NO_PSEUDO_IMPORT_IN_NESTED_SCOPE(), { + node: rule, + }); } }, prepareAST({ node, toRemove }) { @@ -138,6 +163,20 @@ export const hooks = createFeature<{ // API +export class StylablePublicApi { + constructor(private stylable: Stylable) {} + public analyze(meta: StylableMeta): AnalyzedImport[] { + return getImportStatements(meta).map(({ request, defaultExport, named, keyframes }) => ({ + from: request, + default: defaultExport, + named, + typed: { + keyframes, + }, + })); + } +} + function isImportStatement(node: postcss.ChildNode): node is postcss.Rule | postcss.AtRule { return ( (node.type === `atrule` && node.name === `st-import`) || @@ -193,7 +232,8 @@ function addImportSymbols(importDef: Imported, context: FeatureContext, dirConte handler(context, localName, importName, importDef); } } else { - context.diagnostics.error(importDef.rule, diagnostics.UNKNOWN_TYPED_IMPORT(type), { + context.diagnostics.report(diagnostics.UNKNOWN_TYPED_IMPORT(type), { + node: importDef.rule, word: type, }); } @@ -203,9 +243,9 @@ function addImportSymbols(importDef: Imported, context: FeatureContext, dirConte function checkForInvalidAsUsage(importDef: Imported, context: FeatureContext) { for (const [local, imported] of Object.entries(importDef.named)) { if (validateCustomPropertyName(imported) && !validateCustomPropertyName(local)) { - context.diagnostics.warn( - importDef.rule, - diagnostics.INVALID_CUSTOM_PROPERTY_AS_VALUE(imported, local) + context.diagnostics.report( + diagnostics.INVALID_CUSTOM_PROPERTY_AS_VALUE(imported, local), + { node: importDef.rule } ); } } @@ -224,11 +264,10 @@ function validateImports(context: FeatureTransformContext) { (decl) => decl.type === 'decl' && decl.prop === PseudoImportDecl.FROM ); - context.diagnostics.warn( - fromDecl || importObj.rule, - diagnostics.UNKNOWN_IMPORTED_FILE(importObj.request), - { word: importObj.request } - ); + context.diagnostics.report(diagnostics.UNKNOWN_IMPORTED_FILE(importObj.request), { + node: fromDecl || importObj.rule, + word: importObj.request, + }); } else if (resolvedImport._kind === 'css') { // warn about unknown named imported symbols for (const name in importObj.named) { @@ -241,10 +280,9 @@ function validateImports(context: FeatureTransformContext) { (decl) => decl.type === 'decl' && decl.prop === PseudoImportDecl.NAMED ); - context.diagnostics.warn( - namedDecl || importObj.rule, + context.diagnostics.report( diagnostics.UNKNOWN_IMPORTED_SYMBOL(origName, importObj.request), - { word: origName } + { node: namedDecl || importObj.rule, word: origName } ); } } diff --git a/packages/core/src/features/st-mixin.ts b/packages/core/src/features/st-mixin.ts index b3b4e7aa6..8f65f271a 100644 --- a/packages/core/src/features/st-mixin.ts +++ b/packages/core/src/features/st-mixin.ts @@ -1,27 +1,26 @@ -import { createFeature, FeatureContext, FeatureTransformContext } from './feature'; +import { createFeature, FeatureTransformContext } from './feature'; import * as STSymbol from './st-symbol'; import type { ImportSymbol } from './st-import'; +import * as STCustomSelector from './st-custom-selector'; +import * as STVar from './st-var'; import type { ElementSymbol } from './css-type'; import type { ClassSymbol } from './css-class'; import { createSubsetAst } from '../helpers/rule'; -import { - diagnostics as MixinHelperDiagnostics, - parseStMixin, - parseStPartialMixin, -} from '../helpers/mixin'; -import { ignoreDeprecationWarn } from '../helpers/deprecation'; +import { scopeNestedSelector } from '../helpers/selector'; +import { mixinHelperDiagnostics, parseStMixin, parseStPartialMixin } from '../helpers/mixin'; import { resolveArgumentsValue } from '../functions'; import { cssObjectToAst } from '../parser'; import * as postcss from 'postcss'; -import type { FunctionNode, WordNode } from 'postcss-value-parser'; +import { FunctionNode, WordNode, stringify } from 'postcss-value-parser'; import { fixRelativeUrls } from '../stylable-assets'; -import { isValidDeclaration, mergeRules, INVALID_MERGE_OF } from '../stylable-utils'; +import { isValidDeclaration, mergeRules, utilDiagnostics } from '../stylable-utils'; import type { StylableMeta } from '../stylable-meta'; -import type { CSSResolve } from '../stylable-resolver'; +import type { CSSResolve, MetaResolvedSymbols } from '../stylable-resolver'; import type { StylableTransformer } from '../stylable-transformer'; import { dirname } from 'path'; -// ToDo: deprecate - stop usage -import type { SRule } from '../deprecated/postcss-ast-extension'; +import { createDiagnosticReporter, Diagnostics } from '../diagnostics'; +import type { Stylable } from '../stylable'; +import { parseCssSelector } from '@tokey/css-selector-parser'; export interface MixinValue { type: string; @@ -31,10 +30,29 @@ export interface MixinValue { originDecl: postcss.Declaration; } -export interface RefedMixin { - mixin: MixinValue; - ref: ImportSymbol | ClassSymbol | ElementSymbol; -} +export type ValidMixinSymbols = ImportSymbol | ClassSymbol | ElementSymbol; + +export type AnalyzedMixin = + | { + valid: true; + data: MixinValue; + symbol: ValidMixinSymbols; + } + | { + valid: false; + data: MixinValue; + symbol: Exclude | undefined; + }; + +export type MixinReflection = + | { + name: string; + kind: 'css-fragment'; + args: Record[]; + optionalArgs: Map; + } + | { name: string; kind: 'js-func'; args: string[]; func: (...args: any[]) => any } + | { name: string; kind: 'invalid'; args: string }; export const MixinType = { ALL: `-st-mixin` as const, @@ -42,64 +60,141 @@ export const MixinType = { }; export const diagnostics = { - VALUE_CANNOT_BE_STRING: MixinHelperDiagnostics.VALUE_CANNOT_BE_STRING, - INVALID_NAMED_PARAMS: MixinHelperDiagnostics.INVALID_NAMED_PARAMS, - INVALID_MERGE_OF: INVALID_MERGE_OF, - PARTIAL_MIXIN_MISSING_ARGUMENTS(type: string) { - return `"${MixinType.PARTIAL}" can only be used with override arguments provided, missing overrides on "${type}"`; - }, - UNKNOWN_MIXIN(name: string) { - return `unknown mixin: "${name}"`; - }, - OVERRIDE_MIXIN(mixinType: string) { - return `override ${mixinType} on same rule`; - }, - FAILED_TO_APPLY_MIXIN(error: string) { - return `could not apply mixin: ${error}`; - }, - JS_MIXIN_NOT_A_FUNC() { - return `js mixin must be a function`; - }, - CIRCULAR_MIXIN(circularPaths: string[]) { - return `circular mixin found: ${circularPaths.join(' --> ')}`; - }, - UNKNOWN_MIXIN_SYMBOL(name: string) { - return `cannot mixin unknown symbol "${name}"`; - }, + VALUE_CANNOT_BE_STRING: mixinHelperDiagnostics.VALUE_CANNOT_BE_STRING, + INVALID_NAMED_PARAMS: mixinHelperDiagnostics.INVALID_NAMED_PARAMS, + INVALID_MERGE_OF: utilDiagnostics.INVALID_MERGE_OF, + PARTIAL_MIXIN_MISSING_ARGUMENTS: createDiagnosticReporter( + '10001', + 'error', + (type: string) => + `"${MixinType.PARTIAL}" can only be used with override arguments provided, missing overrides on "${type}"` + ), + UNKNOWN_MIXIN: createDiagnosticReporter( + '10002', + 'error', + (name: string) => `unknown mixin: "${name}"` + ), + OVERRIDE_MIXIN: createDiagnosticReporter( + '10003', + 'warning', + (mixinType: string) => `override ${mixinType} on same rule` + ), + FAILED_TO_APPLY_MIXIN: createDiagnosticReporter( + '10004', + 'error', + (error: string) => `could not apply mixin: ${error}` + ), + JS_MIXIN_NOT_A_FUNC: createDiagnosticReporter( + '10005', + 'error', + () => `js mixin must be a function` + ), + UNSUPPORTED_MIXIN_SYMBOL: createDiagnosticReporter( + '10007', + 'error', + (name: string, symbolType: STSymbol.StylableSymbol['_kind']) => + `cannot mix unsupported symbol "${name}" of type "${STSymbol.readableTypeMap[symbolType]}"` + ), + CIRCULAR_MIXIN: createDiagnosticReporter( + '10006', + 'error', + (circularPaths: string[]) => `circular mixin found: ${circularPaths.join(' --> ')}` + ), + UNKNOWN_ARG: createDiagnosticReporter( + '10009', + 'warning', + (argName) => `unknown mixin argument "${argName}"` + ), }; // HOOKS export const hooks = createFeature({ - analyzeDeclaration({ context, decl }) { - ignoreDeprecationWarn(() => { - const parentRule = decl.parent as SRule; - const prevMixins = parentRule?.mixins || []; - const mixins = collectDeclMixins( - context, - decl, - (mixinSymbolName) => { - const symbol = STSymbol.get(context.meta, mixinSymbolName); - return symbol?._kind === 'import' && !symbol.import.from.match(/.css$/) - ? 'args' - : 'named'; - }, - false /*dont report param signature diagnostics*/, - prevMixins - ); - if (mixins.length) { - parentRule.mixins = mixins; - } - }); - }, transformLastPass({ context, ast, transformer, cssVarsMapping, path }) { ast.walkRules((rule) => appendMixins(context, transformer, rule, cssVarsMapping, path)); }, }); // API +export class StylablePublicApi { + constructor(private stylable: Stylable) {} + public resolveExpr( + meta: StylableMeta, + expr: string, + { + diagnostics = new Diagnostics(), + resolveOptionalArgs = false, + }: { diagnostics?: Diagnostics; resolveOptionalArgs?: boolean } = {} + ) { + const resolvedSymbols = this.stylable.resolver.resolveSymbols(meta, diagnostics); + const { mainNamespace } = resolvedSymbols; + const analyzedMixins = collectDeclMixins( + { meta, diagnostics }, + resolvedSymbols, + postcss.decl({ prop: '-st-mixin', value: expr }), + (mixinSymbolName) => (mainNamespace[mixinSymbolName] === 'js' ? 'args' : 'named') + ); + const result: MixinReflection[] = []; + for (const { data } of analyzedMixins) { + const name = data.type; + const symbolKind = mainNamespace[name]; + if (symbolKind === 'class' || symbolKind === 'element') { + const mixRef: MixinReflection = { + name, + kind: 'css-fragment', + args: [], + optionalArgs: new Map(), + }; + for (const [argName, argValue] of Object.entries(data.options)) { + mixRef.args.push({ [argName]: argValue }); + } + if (resolveOptionalArgs) { + const varMap = new Map(); + const resolveChain = resolvedSymbols[symbolKind][name]; + getCSSMixinRoots(meta, resolveChain, ({ mixinRoot }) => { + const names = new Set(); + collectOptionalArgs( + { meta, resolver: this.stylable.resolver }, + mixinRoot, + names + ); + names.forEach((name) => varMap.set(name, { name })); + }); + mixRef.optionalArgs = varMap; + } + result.push(mixRef); + } else if ( + symbolKind === 'js' && + typeof resolvedSymbols.js[name].symbol === 'function' + ) { + const mixRef: MixinReflection = { + name, + kind: 'js-func', + args: [], + func: resolvedSymbols.js[name].symbol, + }; + for (const arg of Object.values(data.options)) { + mixRef.args.push(arg.value); + } + result.push(mixRef); + } else { + result.push({ + name, + kind: 'invalid', + args: + data.valueNode?.type === 'function' ? stringify(data.valueNode.nodes) : '', + }); + } + } + return result; + } + public scopeNestedSelector(scopeSelector: string, nestSelector: string): string { + return scopeNestedSelector(parseCssSelector(scopeSelector), parseCssSelector(nestSelector)) + .selector; + } +} -export function appendMixins( +function appendMixins( context: FeatureTransformContext, transformer: StylableTransformer, rule: postcss.Rule, @@ -111,7 +206,9 @@ export function appendMixins( return; } for (const mixin of mixins) { - appendMixin(context, { transformer, mixDef: mixin, rule, path, cssPropertyMapping }); + if (mixin.valid) { + appendMixin(context, { transformer, mixDef: mixin, rule, path, cssPropertyMapping }); + } } for (const mixinDecl of decls) { mixinDecl.remove(); @@ -121,20 +218,21 @@ export function appendMixins( function collectRuleMixins( context: FeatureTransformContext, rule: postcss.Rule -): [decls: postcss.Declaration[], mixins: RefedMixin[]] { - let mixins: RefedMixin[] = []; - const { mainNamespace } = context.getResolvedSymbols(context.meta); +): [decls: postcss.Declaration[], mixins: AnalyzedMixin[]] { + let mixins: AnalyzedMixin[] = []; + const resolvedSymbols = context.getResolvedSymbols(context.meta); + const { mainNamespace } = resolvedSymbols; const decls: postcss.Declaration[] = []; rule.walkDecls((decl) => { if (decl.prop === `-st-mixin` || decl.prop === `-st-partial-mixin`) { decls.push(decl); mixins = collectDeclMixins( context, + resolvedSymbols, decl, (mixinSymbolName) => { return mainNamespace[mixinSymbolName] === 'js' ? 'args' : 'named'; }, - true /* report param signature diagnostics */, mixins ); } @@ -143,14 +241,14 @@ function collectRuleMixins( } function collectDeclMixins( - context: FeatureContext, + context: Pick, + resolvedSymbols: MetaResolvedSymbols, decl: postcss.Declaration, paramSignature: (mixinSymbolName: string) => 'named' | 'args', - isTransformPhase: boolean, - previousMixins?: RefedMixin[] -): RefedMixin[] { + previousMixins?: AnalyzedMixin[] +): AnalyzedMixin[] { const { meta } = context; - let mixins: RefedMixin[] = []; + let mixins: AnalyzedMixin[] = []; const parser = decl.prop === MixinType.ALL ? parseStMixin @@ -161,47 +259,72 @@ function collectDeclMixins( return previousMixins || mixins; } - parser(decl, paramSignature, context.diagnostics, isTransformPhase).forEach((mixin) => { - const mixinRefSymbol = STSymbol.get(meta, mixin.type); - if ( - mixinRefSymbol && - (mixinRefSymbol._kind === 'import' || - mixinRefSymbol._kind === 'class' || - mixinRefSymbol._kind === 'element') - ) { - if (mixin.partial && Object.keys(mixin.options).length === 0) { - context.diagnostics.warn( - decl, - diagnostics.PARTIAL_MIXIN_MISSING_ARGUMENTS(mixin.type), - { + parser(decl, paramSignature, context.diagnostics, /*emitStrategyDiagnostics*/ true).forEach( + (mixin) => { + const mixinRefSymbol = STSymbol.get(meta, mixin.type); + const symbolName = mixin.type; + const resolvedType = resolvedSymbols.mainNamespace[symbolName]; + if ( + resolvedType && + ((resolvedType === 'js' && + typeof resolvedSymbols.js[symbolName].symbol === 'function') || + resolvedType === 'class' || + resolvedType === 'element') + ) { + mixins.push({ + valid: true, + data: mixin, + symbol: mixinRefSymbol as ValidMixinSymbols, + }); + if (mixin.partial && Object.keys(mixin.options).length === 0) { + context.diagnostics.report( + diagnostics.PARTIAL_MIXIN_MISSING_ARGUMENTS(mixin.type), + { + node: decl, + word: mixin.type, + } + ); + } + } else { + mixins.push({ + valid: false, + data: mixin, + symbol: mixinRefSymbol as + | Exclude + | undefined, + }); + if (resolvedType === 'js') { + context.diagnostics.report(diagnostics.JS_MIXIN_NOT_A_FUNC(), { + node: decl, word: mixin.type, - } - ); - } - const refedMixin = { - mixin, - ref: mixinRefSymbol, - }; - mixins.push(refedMixin); - if (!isTransformPhase) { - ignoreDeprecationWarn(() => meta.mixins).push(refedMixin); + }); + } else if (resolvedType) { + context.diagnostics.report( + diagnostics.UNSUPPORTED_MIXIN_SYMBOL(mixin.type, resolvedType), + { + node: decl, + word: mixin.type, + } + ); + } else { + context.diagnostics.report(diagnostics.UNKNOWN_MIXIN(mixin.type), { + node: decl, + word: mixin.type, + }); + } } - } else { - context.diagnostics.warn(decl, diagnostics.UNKNOWN_MIXIN(mixin.type), { - word: mixin.type, - }); } - }); + ); if (previousMixins) { - const partials = previousMixins.filter((r) => r.mixin.partial); - const nonPartials = previousMixins.filter((r) => !r.mixin.partial); + const partials = previousMixins.filter((r) => r.data.partial); + const nonPartials = previousMixins.filter((r) => !r.data.partial); const isInPartial = decl.prop === MixinType.PARTIAL; if ( (partials.length && decl.prop === MixinType.PARTIAL) || (nonPartials.length && decl.prop === MixinType.ALL) ) { - context.diagnostics.warn(decl, diagnostics.OVERRIDE_MIXIN(decl.prop)); + context.diagnostics.report(diagnostics.OVERRIDE_MIXIN(decl.prop), { node: decl }); } if (partials.length && nonPartials.length) { mixins = isInPartial ? nonPartials.concat(mixins) : partials.concat(mixins); @@ -216,18 +339,18 @@ function collectDeclMixins( interface ApplyMixinContext { transformer: StylableTransformer; - mixDef: RefedMixin; + mixDef: AnalyzedMixin & { valid: true }; rule: postcss.Rule; path: string[]; cssPropertyMapping: Record; } -export function appendMixin(context: FeatureTransformContext, config: ApplyMixinContext) { +function appendMixin(context: FeatureTransformContext, config: ApplyMixinContext) { if (checkRecursive(context, config)) { return; } const resolvedSymbols = context.getResolvedSymbols(context.meta); - const symbolName = config.mixDef.mixin.type; + const symbolName = config.mixDef.data.type; const resolvedType = resolvedSymbols.mainNamespace[symbolName]; if (resolvedType === `class` || resolvedType === `element`) { const resolveChain = resolvedSymbols[resolvedType][symbolName]; @@ -239,28 +362,15 @@ export function appendMixin(context: FeatureTransformContext, config: ApplyMixin try { handleJSMixin(context, config, resolvedMixin.symbol); } catch (e) { - context.diagnostics.error( - config.rule, - diagnostics.FAILED_TO_APPLY_MIXIN(String(e)), - { - word: config.mixDef.mixin.type, - } - ); + context.diagnostics.report(diagnostics.FAILED_TO_APPLY_MIXIN(String(e)), { + node: config.rule, + word: config.mixDef.data.type, + }); return; } - } else { - context.diagnostics.error(config.rule, diagnostics.JS_MIXIN_NOT_A_FUNC(), { - word: config.mixDef.mixin.type, - }); } return; } - - // ToDo: report on unsupported mixed in symbol type - const mixinDecl = config.mixDef.mixin.originDecl; - context.diagnostics.error(mixinDecl, diagnostics.UNKNOWN_MIXIN_SYMBOL(mixinDecl.value), { - word: mixinDecl.value, - }); } function checkRecursive( @@ -268,15 +378,16 @@ function checkRecursive( { mixDef, path, rule }: ApplyMixinContext ) { const symbolName = - mixDef.ref.name === meta.root - ? mixDef.ref._kind === 'class' + mixDef.symbol.name === meta.root + ? mixDef.symbol._kind === 'class' ? meta.root : 'default' - : mixDef.mixin.type; + : mixDef.data.type; const isRecursive = path.includes(symbolName + ' from ' + meta.source); if (isRecursive) { // Todo: add test verifying word - report.warn(rule, diagnostics.CIRCULAR_MIXIN(path), { + report.report(diagnostics.CIRCULAR_MIXIN(path), { + node: rule, word: symbolName, }); return true; @@ -292,7 +403,7 @@ function handleJSMixin( const stVarOverride = context.evaluator.stVarOverride || {}; const meta = context.meta; const mixDef = config.mixDef; - const res = mixinFunction((mixDef.mixin.options as any[]).map((v) => v.value)); + const res = mixinFunction((mixDef.data.options as any[]).map((v) => v.value)); const mixinRoot = cssObjectToAst(res).root; mixinRoot.walkDecls((decl) => { @@ -302,14 +413,14 @@ function handleJSMixin( }); config.transformer.transformAst(mixinRoot, meta, undefined, stVarOverride, [], true); - const mixinPath = (mixDef.ref as ImportSymbol).import.request; + const mixinPath = (mixDef.symbol as ImportSymbol).import.request; fixRelativeUrls( mixinRoot, context.resolver.resolvePath(dirname(meta.source), mixinPath), meta.source ); - mergeRules(mixinRoot, config.rule, mixDef.mixin.originDecl, context.diagnostics); + mergeRules(mixinRoot, config.rule, mixDef.data.originDecl, context.diagnostics); } function handleCSSMixin( @@ -318,80 +429,119 @@ function handleCSSMixin( resolveChain: CSSResolve[] ) { const mixDef = config.mixDef; - const isPartial = mixDef.mixin.partial; - const namedArgs = mixDef.mixin.options as Record; + const isPartial = mixDef.data.partial; + const namedArgs = mixDef.data.options as Record; const overrideKeys = Object.keys(namedArgs); if (isPartial && overrideKeys.length === 0) { return; } - const roots = []; - for (const resolved of resolveChain) { - roots.push(createMixinRootFromCSSResolve(context, config, resolved)); - if (resolved.symbol[`-st-extends`]) { - break; + const optionalArgs = new Set(); + const roots = getCSSMixinRoots( + context.meta, + resolveChain, + ({ mixinRoot, resolvedClass, isRootMixin }) => { + const stVarOverride = context.evaluator.stVarOverride || {}; + const mixDef = config.mixDef; + const namedArgs = mixDef.data.options as Record; + + if (mixDef.data.partial) { + filterPartialMixinDecl(context.meta, mixinRoot, Object.keys(namedArgs)); + } + + // resolve override args + const resolvedArgs = resolveArgumentsValue( + namedArgs, + config.transformer, + context.meta, + context.diagnostics, + mixDef.data.originDecl, + stVarOverride, + config.path, + config.cssPropertyMapping + ); + collectOptionalArgs( + { meta: resolvedClass.meta, resolver: context.resolver }, + mixinRoot, + optionalArgs + ); + // transform mixin + const mixinMeta: StylableMeta = resolvedClass.meta; + const symbolName = + isRootMixin && resolvedClass.meta !== context.meta ? 'default' : mixDef.data.type; + config.transformer.transformAst( + mixinRoot, + mixinMeta, + undefined, + resolvedArgs, + config.path.concat(symbolName + ' from ' + context.meta.source), + true, + resolvedClass.symbol.name + ); + fixRelativeUrls(mixinRoot, resolvedClass.meta.source, context.meta.source); + } + ); + + for (const overrideArg of overrideKeys) { + if (!optionalArgs.has(overrideArg)) { + context.diagnostics.report(diagnostics.UNKNOWN_ARG(overrideArg), { + node: mixDef.data.originDecl, + word: overrideArg, + }); } } if (roots.length === 1) { - mergeRules(roots[0], config.rule, mixDef.mixin.originDecl, config.transformer.diagnostics); + mergeRules(roots[0], config.rule, mixDef.data.originDecl, config.transformer.diagnostics); } else if (roots.length > 1) { const mixinRoot = postcss.root(); roots.forEach((root) => mixinRoot.prepend(...root.nodes)); - mergeRules(mixinRoot, config.rule, mixDef.mixin.originDecl, config.transformer.diagnostics); + mergeRules(mixinRoot, config.rule, mixDef.data.originDecl, config.transformer.diagnostics); } } -function createMixinRootFromCSSResolve( - context: FeatureTransformContext, - config: ApplyMixinContext, - resolvedClass: CSSResolve +function collectOptionalArgs( + context: Pick, + mixinRoot: postcss.Root, + optionalArgs: Set = new Set() ) { - const stVarOverride = context.evaluator.stVarOverride || {}; - const meta = context.meta; - const mixDef = config.mixDef; - const isRootMixin = resolvedClass.symbol.name === resolvedClass.meta.root; - const mixinRoot = createSubsetAst( - resolvedClass.meta.ast, - (resolvedClass.symbol._kind === 'class' ? '.' : '') + resolvedClass.symbol.name, - undefined, - isRootMixin - ); - - const namedArgs = mixDef.mixin.options as Record; + mixinRoot.walkDecls((decl) => { + const varNames = STVar.parseVarsFromExpr(decl.value); + for (const name of varNames) { + for (const refName of STVar.resolveReferencedVarNames(context, name)) { + optionalArgs.add(refName); + } + } + }); +} - if (mixDef.mixin.partial) { - filterPartialMixinDecl(meta, mixinRoot, Object.keys(namedArgs)); +function getCSSMixinRoots( + contextMeta: StylableMeta, + resolveChain: CSSResolve[], + processMixinRoot: (data: { + mixinRoot: postcss.Root; + resolvedClass: CSSResolve; + isRootMixin: boolean; + }) => void +) { + const roots = []; + for (const resolved of resolveChain) { + const isRootMixin = resolved.symbol.name === resolved.meta.root; + const mixinRoot = createSubsetAst( + resolved.meta.sourceAst, + (resolved.symbol._kind === 'class' ? '.' : '') + resolved.symbol.name, + undefined, + isRootMixin, + (name) => STCustomSelector.getCustomSelector(contextMeta, name) + ); + processMixinRoot({ mixinRoot, resolvedClass: resolved, isRootMixin }); + roots.push(mixinRoot); + if (resolved.symbol[`-st-extends`]) { + break; + } } - - const resolvedArgs = resolveArgumentsValue( - namedArgs, - config.transformer, - context.meta, - context.diagnostics, - mixDef.mixin.originDecl, - stVarOverride, - config.path, - config.cssPropertyMapping - ); - - const mixinMeta: StylableMeta = resolvedClass.meta; - const symbolName = isRootMixin && resolvedClass.meta !== meta ? 'default' : mixDef.mixin.type; - - config.transformer.transformAst( - mixinRoot, - mixinMeta, - undefined, - resolvedArgs, - config.path.concat(symbolName + ' from ' + meta.source), - true, - resolvedClass.symbol.name - ); - - fixRelativeUrls(mixinRoot, mixinMeta.source, meta.source); - - return mixinRoot; + return roots; } /** we assume that mixinRoot is freshly created nodes from the ast */ diff --git a/packages/core/src/features/st-scope.ts b/packages/core/src/features/st-scope.ts index 1e1e8ca38..cc6640ce4 100644 --- a/packages/core/src/features/st-scope.ts +++ b/packages/core/src/features/st-scope.ts @@ -1,13 +1,17 @@ import { createFeature } from './feature'; import { parseSelectorWithCache, scopeNestedSelector } from '../helpers/selector'; +import type { Stylable } from '../stylable'; import type { ImmutablePseudoClass } from '@tokey/css-selector-parser'; import * as postcss from 'postcss'; import type { SRule } from '../deprecated/postcss-ast-extension'; +import { createDiagnosticReporter } from '../diagnostics'; export const diagnostics = { - MISSING_SCOPING_PARAM() { - return '"@st-scope" missing scoping selector parameter'; - }, + MISSING_SCOPING_PARAM: createDiagnosticReporter( + '11009', + 'error', + () => '"@st-scope" missing scoping selector parameter' + ), }; // HOOKS @@ -18,7 +22,7 @@ export const hooks = createFeature<{ IMMUTABLE_SELECTOR: ImmutablePseudoClass }> return; } if (!atRule.params) { - context.diagnostics.warn(atRule, diagnostics.MISSING_SCOPING_PARAM()); + context.diagnostics.report(diagnostics.MISSING_SCOPING_PARAM(), { node: atRule }); } analyzeRule( postcss.rule({ @@ -41,7 +45,14 @@ export const hooks = createFeature<{ IMMUTABLE_SELECTOR: ImmutablePseudoClass }> // API -function isStScopeStatement(node: postcss.ChildNode): node is postcss.AtRule { +export class StylablePublicApi { + constructor(private stylable: Stylable) {} + public getStScope(rule: postcss.Rule) { + return getStScope(rule); + } +} + +function isStScopeStatement(node: any): node is postcss.AtRule { return node.type === 'atrule' && node.name === 'st-scope'; } @@ -57,3 +68,14 @@ function flattenScope(atRule: postcss.AtRule) { }); } } + +function getStScope(rule: postcss.Rule): postcss.AtRule | undefined { + let current: postcss.Container | postcss.Document = rule; + while (current?.parent) { + current = current.parent; + if (isStScopeStatement(current) && current.parent?.type === 'root') { + return current; + } + } + return; +} diff --git a/packages/core/src/features/st-symbol.ts b/packages/core/src/features/st-symbol.ts index 3aa58fe78..030e756bc 100644 --- a/packages/core/src/features/st-symbol.ts +++ b/packages/core/src/features/st-symbol.ts @@ -7,9 +7,9 @@ import type { CSSVarSymbol } from './css-custom-property'; import type { KeyframesSymbol } from './css-keyframes'; import type { LayerSymbol } from './css-layer'; import { plugableRecord } from '../helpers/plugable-record'; -import { ignoreDeprecationWarn } from '../helpers/deprecation'; import type { StylableMeta } from '../stylable-meta'; import type * as postcss from 'postcss'; +import { createDiagnosticReporter } from '../diagnostics'; // SYMBOLS DEFINITION @@ -32,6 +32,15 @@ const NAMESPACES = { layer: `layer`, var: `main`, } as const; +export const readableTypeMap: Record = { + class: 'css class', + element: 'css element type', + cssVar: 'css custom property', + import: 'stylable imported symbol', + keyframes: 'css keyframes', + layer: 'css layer', + var: 'stylable var', +}; // state structure function createState(clone?: State): State { return { @@ -102,12 +111,16 @@ interface State { const dataKey = plugableRecord.key('mappedSymbols'); export const diagnostics = { - REDECLARE_SYMBOL(name: string) { - return `redeclare symbol "${name}"`; - }, - REDECLARE_ROOT() { - return `root is used for the stylesheet and cannot be overridden`; - }, + REDECLARE_SYMBOL: createDiagnosticReporter( + '06001', + 'warning', + (name: string) => `redeclare symbol "${name}"` + ), + REDECLARE_ROOT: createDiagnosticReporter( + '06002', + 'error', + () => `root is used for the stylesheet and cannot be overridden` + ), }; // HOOKS @@ -158,18 +171,15 @@ export function addSymbol({ const typeTable = byType[symbol._kind]; const nsName = NAMESPACES[symbol._kind]; if (node && name === `root` && nsName === `main` && byNSFlat[nsName][name]) { - context.diagnostics.warn(node, diagnostics.REDECLARE_ROOT(), { word: `root` }); + context.diagnostics.report(diagnostics.REDECLARE_ROOT(), { + node, + word: `root`, + }); return; } byNS[nsName].push({ name, symbol, ast: node, safeRedeclare }); byNSFlat[nsName][name] = symbol; typeTable[name] = symbol; - // deprecated - if (nsName === `main`) { - ignoreDeprecationWarn(() => { - context.meta.mappedSymbols[name] = symbol; - }); - } } export function reportRedeclare(context: FeatureContext) { @@ -188,7 +198,8 @@ export function reportRedeclare(context: FeatureContext) { for (const name of collisions) { for (const { safeRedeclare, ast } of flat[name]) { if (!safeRedeclare && ast) { - context.diagnostics.warn(ast, diagnostics.REDECLARE_SYMBOL(name), { + context.diagnostics.report(diagnostics.REDECLARE_SYMBOL(name), { + node: ast, word: name, }); } diff --git a/packages/core/src/features/st-var.ts b/packages/core/src/features/st-var.ts index bba9e5426..fb9146cc7 100644 --- a/packages/core/src/features/st-var.ts +++ b/packages/core/src/features/st-var.ts @@ -9,14 +9,14 @@ import { isChildOfAtRule } from '../helpers/rule'; import { walkSelector } from '../helpers/selector'; import { stringifyFunction, getStringValue, strategies } from '../helpers/value'; import { stripQuotation } from '../helpers/string'; -import { ignoreDeprecationWarn } from '../helpers/deprecation'; import type { ImmutablePseudoClass, PseudoClass } from '@tokey/css-selector-parser'; import type * as postcss from 'postcss'; import { processDeclarationFunctions } from '../process-declaration-functions'; -import { Diagnostics } from '../diagnostics'; +import { createDiagnosticReporter, Diagnostics } from '../diagnostics'; import type { ParsedValue } from '../types'; import type { Stylable } from '../stylable'; import type { RuntimeStVar } from '../stylable-transformer'; +import postcssValueParser from 'postcss-value-parser'; export interface VarSymbol { _kind: 'var'; @@ -45,26 +45,57 @@ export interface FlatComputedStVar { export const diagnostics = { FORBIDDEN_DEF_IN_COMPLEX_SELECTOR: generalDiagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR, - NO_VARS_DEF_IN_ST_SCOPE() { - return `cannot define ":vars" inside of "@st-scope"`; - }, - DEPRECATED_ST_FUNCTION_NAME: (name: string, alternativeName: string) => { - return `"${name}" is deprecated, use "${alternativeName}"`; - }, - CYCLIC_VALUE: (cyclicChain: string[]) => - `Cyclic value definition detected: "${cyclicChain - .map((s, i) => (i === cyclicChain.length - 1 ? '↻ ' : i === 0 ? '→ ' : '↪ ') + s) - .join('\n')}"`, - MISSING_VAR_IN_VALUE: () => `invalid value() with no var identifier`, - COULD_NOT_RESOLVE_VALUE: (args?: string) => - `cannot resolve value function${args ? ` using the arguments provided: "${args}"` : ''}`, - MULTI_ARGS_IN_VALUE: (args: string) => - `value function accepts only a single argument: "value(${args})"`, - CANNOT_USE_AS_VALUE: (type: string, varName: string) => - `${type} "${varName}" cannot be used as a variable`, - CANNOT_USE_JS_AS_VALUE: (type: string, varName: string) => - `JavaScript ${type} import "${varName}" cannot be used as a variable`, - UNKNOWN_VAR: (name: string) => `unknown var "${name}"`, + NO_VARS_DEF_IN_ST_SCOPE: createDiagnosticReporter( + '07002', + 'error', + () => `cannot define ":vars" inside of "@st-scope"` + ), + DEPRECATED_ST_FUNCTION_NAME: createDiagnosticReporter( + '07003', + 'info', + (name: string, alternativeName: string) => + `"${name}" is deprecated, use "${alternativeName}"` + ), + CYCLIC_VALUE: createDiagnosticReporter( + '07004', + 'error', + (cyclicChain: string[]) => + `Cyclic value definition detected: "${cyclicChain + .map((s, i) => (i === cyclicChain.length - 1 ? '↻ ' : i === 0 ? '→ ' : '↪ ') + s) + .join('\n')}"` + ), + MISSING_VAR_IN_VALUE: createDiagnosticReporter( + '07005', + 'error', + () => `invalid value() with no var identifier` + ), + COULD_NOT_RESOLVE_VALUE: createDiagnosticReporter( + '07006', + 'error', + (args?: string) => + `cannot resolve value function${args ? ` using the arguments provided: "${args}"` : ''}` + ), + MULTI_ARGS_IN_VALUE: createDiagnosticReporter( + '07007', + 'error', + (args: string) => `value function accepts only a single argument: "value(${args})"` + ), + CANNOT_USE_AS_VALUE: createDiagnosticReporter( + '07008', + 'error', + (type: string, varName: string) => `${type} "${varName}" cannot be used as a variable` + ), + CANNOT_USE_JS_AS_VALUE: createDiagnosticReporter( + '07009', + 'error', + (type: string, varName: string) => + `JavaScript ${type} import "${varName}" cannot be used as a variable` + ), + UNKNOWN_VAR: createDiagnosticReporter( + '07010', + 'error', + (name: string) => `unknown var "${name}"` + ), }; // HOOKS @@ -81,14 +112,16 @@ export const hooks = createFeature<{ // make sure `:vars` is the only selector if (rule.selector === `:vars`) { if (isChildOfAtRule(rule, `st-scope`)) { - context.diagnostics.warn(rule, diagnostics.NO_VARS_DEF_IN_ST_SCOPE()); + context.diagnostics.report(diagnostics.NO_VARS_DEF_IN_ST_SCOPE(), { node: rule }); } else { collectVarSymbols(context, rule); } // stop further walk into `:vars {}` return walkSelector.stopAll; } else { - context.diagnostics.warn(rule, diagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR(`:vars`)); + context.diagnostics.report(diagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR(`:vars`), { + node: rule, + }); } return; }, @@ -109,6 +142,7 @@ export const hooks = createFeature<{ for (const name of Object.keys(symbols)) { const symbol = symbols[name]; const evaluated = context.evaluator.evaluateValue(noDaigContext, { + // ToDo: change to `value(${name})` in order to fix overrides in exports value: stripQuotation(symbol.text), meta: context.meta, node: symbol.node, @@ -216,6 +250,26 @@ export class StylablePublicApi { } } +export function parseVarsFromExpr(expr: string) { + const nameSet = new Set(); + postcssValueParser(expr).walk((node) => { + if (node.type === 'function' && node.value === 'value') { + for (const argNode of node.nodes) { + switch (argNode.type) { + case 'word': + nameSet.add(argNode.value); + return; + case 'div': + if (argNode.value === ',') { + return; + } + } + } + } + }); + return nameSet; +} + function collectVarSymbols(context: FeatureContext, rule: postcss.Rule) { rule.walkDecls((decl) => { collectUrls(context.meta, decl); // ToDo: remove @@ -244,10 +298,6 @@ function collectVarSymbols(context: FeatureContext, rule: postcss.Rule) { }, node: decl, }); - // deprecated - ignoreDeprecationWarn(() => { - context.meta.vars.push(STSymbol.get(context.meta, name, `var`)!); - }); }); } @@ -257,10 +307,12 @@ function warnOnDeprecatedCustomValues(context: FeatureContext, decl: postcss.Dec (node) => { if (node.type === 'nested-item' && deprecatedStFunctions[node.name]) { const { alternativeName } = deprecatedStFunctions[node.name]; - context.diagnostics.info( - decl, + context.diagnostics.report( diagnostics.DEPRECATED_ST_FUNCTION_NAME(node.name, alternativeName), - { word: node.name } + { + node: decl, + word: node.name, + } ); } }, @@ -295,7 +347,8 @@ function evaluateValueCall( // check var not empty if (!varName) { if (node) { - context.diagnostics.warn(node, diagnostics.MISSING_VAR_IN_VALUE(), { + context.diagnostics.report(diagnostics.MISSING_VAR_IN_VALUE(), { + node, word: getStringValue(parsedNode), }); } @@ -335,7 +388,9 @@ function evaluateValueCall( if (node) { const argsAsString = parsedArgs.join(', '); if (!typeError && !topLevelType && parsedArgs.length > 1) { - context.diagnostics.warn(node, diagnostics.MULTI_ARGS_IN_VALUE(argsAsString)); + context.diagnostics.report(diagnostics.MULTI_ARGS_IN_VALUE(argsAsString), { + node, + }); } } @@ -354,10 +409,10 @@ function evaluateValueCall( } else if (node) { // unsupported Javascript value // ToDo: provide actual exported id (default/named as x) - context.diagnostics.warn( - node, + context.diagnostics.report( diagnostics.CANNOT_USE_JS_AS_VALUE(importedType, varName), { + node, word: varName, } ); @@ -380,18 +435,58 @@ function evaluateValueCall( reportUnsupportedSymbolInValue(context, varName, finalResolve, node); } else if (node) { // report unknown var - context.diagnostics.error(node, diagnostics.UNKNOWN_VAR(varName), { + context.diagnostics.report(diagnostics.UNKNOWN_VAR(varName), { + node, word: varName, }); } } else if (node) { - context.diagnostics.warn(node, diagnostics.UNKNOWN_VAR(varName), { + context.diagnostics.report(diagnostics.UNKNOWN_VAR(varName), { + node, word: varName, }); } } } +export function resolveReferencedVarNames( + context: Pick, + initialName: string +) { + const refNames = new Set(); + const varsToCheck: { meta: StylableMeta; name: string }[] = [ + { meta: context.meta, name: initialName }, + ]; + const checked = new Set(); + while (varsToCheck.length) { + const { meta, name } = varsToCheck.shift()!; + const contextualId = meta.source + '/' + name; + if (!checked.has(contextualId)) { + checked.add(contextualId); + refNames.add(name); + const symbol = STSymbol.get(meta, name); + switch (symbol?._kind) { + case 'var': + parseVarsFromExpr(symbol.text).forEach((refName) => + varsToCheck.push({ + meta, + name: refName, + }) + ); + break; + case 'import': { + const resolved = context.resolver.deepResolve(symbol); + if (resolved?._kind === 'css' && resolved.symbol._kind === 'var') { + varsToCheck.push({ meta: resolved.meta, name: resolved.symbol.name }); + } + break; + } + } + } + } + return refNames; +} + function reportUnsupportedSymbolInValue( context: FeatureTransformContext, name: string, @@ -401,7 +496,8 @@ function reportUnsupportedSymbolInValue( const symbol = resolve.symbol; const errorKind = symbol._kind === 'class' && symbol[`-st-root`] ? 'stylesheet' : symbol._kind; if (node) { - context.diagnostics.warn(node, diagnostics.CANNOT_USE_AS_VALUE(errorKind, name), { + context.diagnostics.report(diagnostics.CANNOT_USE_AS_VALUE(errorKind, name), { + node, word: name, }); } @@ -418,7 +514,8 @@ function handleCyclicValues( if (node) { const cyclicChain = passedThrough.map((variable) => variable || ''); cyclicChain.push(refUniqID); - context.diagnostics.warn(node, diagnostics.CYCLIC_VALUE(cyclicChain), { + context.diagnostics.report(diagnostics.CYCLIC_VALUE(cyclicChain), { + node, word: refUniqID, // ToDo: check word is path+var and not var name }); } diff --git a/packages/core/src/functions.ts b/packages/core/src/functions.ts index b39238738..7390c95bc 100644 --- a/packages/core/src/functions.ts +++ b/packages/core/src/functions.ts @@ -1,13 +1,11 @@ import { dirname, relative } from 'path'; import postcssValueParser from 'postcss-value-parser'; import type * as postcss from 'postcss'; -import { Diagnostics } from './diagnostics'; +import { createDiagnosticReporter, Diagnostics } from './diagnostics'; import { isCssNativeFunction } from './native-reserved-lists'; import { assureRelativeUrlPrefix } from './stylable-assets'; import type { StylableMeta } from './stylable-meta'; import { - CSSResolve, - JSResolve, StylableResolver, createSymbolResolverWithCache, MetaResolvedSymbols, @@ -20,9 +18,6 @@ import type { FeatureTransformContext } from './features/feature'; import { CSSCustomProperty, STVar } from './features'; import { unbox, CustomValueError } from './custom-values'; -export type ValueFormatter = (name: string) => string; -export type ResolvedFormatter = Record; - export interface EvalValueData { value: string; passedThrough: string[]; @@ -72,11 +67,18 @@ export class StylableEvaluator { // old API -export const functionWarnings = { - FAIL_TO_EXECUTE_FORMATTER: (resolvedValue: string, message: string) => - `failed to execute formatter "${resolvedValue}" with error: "${message}"`, - UNKNOWN_FORMATTER: (name: string) => - `cannot find native function or custom formatter called ${name}`, +export const functionDiagnostics = { + FAIL_TO_EXECUTE_FORMATTER: createDiagnosticReporter( + '15001', + 'error', + (resolvedValue: string, message: string) => + `failed to execute formatter "${resolvedValue}" with error: "${message}"` + ), + UNKNOWN_FORMATTER: createDiagnosticReporter( + '15002', + 'error', + (name: string) => `cannot find native function or custom formatter called ${name}` + ), }; export function resolveArgumentsValue( @@ -187,13 +189,15 @@ export function processDeclarationValue( } catch (error) { parsedNode.resolvedValue = stringifyFunction(value, parsedNode); if (diagnostics && node) { - diagnostics.warn( - node, - functionWarnings.FAIL_TO_EXECUTE_FORMATTER( + diagnostics.report( + functionDiagnostics.FAIL_TO_EXECUTE_FORMATTER( parsedNode.resolvedValue, (error as Error)?.message ), - { word: (node as postcss.Declaration).value } + { + node, + word: (node as postcss.Declaration).value, + } ); } } @@ -224,7 +228,8 @@ export function processDeclarationValue( parsedNode.resolvedValue = stringifyFunction(value, parsedNode); } else if (node) { parsedNode.resolvedValue = stringifyFunction(value, parsedNode); - diagnostics.warn(node, functionWarnings.UNKNOWN_FORMATTER(value), { + diagnostics.report(functionDiagnostics.UNKNOWN_FORMATTER(value), { + node, word: value, }); } @@ -263,12 +268,14 @@ export function processDeclarationValue( const invalidNode = initialNode || node; if (invalidNode) { - diagnostics.warn( - invalidNode, + diagnostics.report( STVar.diagnostics.COULD_NOT_RESOLVE_VALUE( [...(rootArgument ? [rootArgument] : []), ...args].join(', ') ), - { word: value } + { + node: invalidNode, + word: value, + } ); } else { // TODO: catch broken variable resolutions without a node diff --git a/packages/core/src/helpers/css-custom-property.ts b/packages/core/src/helpers/css-custom-property.ts index 464740908..da01098fa 100644 --- a/packages/core/src/helpers/css-custom-property.ts +++ b/packages/core/src/helpers/css-custom-property.ts @@ -1,5 +1,5 @@ import type * as postcss from 'postcss'; -import type { Diagnostics } from '../diagnostics'; +import { createDiagnosticReporter, Diagnostics } from '../diagnostics'; import { stripQuotation } from '../helpers/string'; const UNIVERSAL_SYNTAX_DEFINITION = '*'; @@ -10,18 +10,29 @@ interface AtPropertyValidationResponse { } export const atPropertyValidationWarnings = { - MISSING_REQUIRED_DESCRIPTOR(descriptorName: string) { - return `@property rules require a "${descriptorName}" descriptor`; - }, - MISSING_REQUIRED_INITIAL_VALUE_DESCRIPTOR() { - return '@property "initial-value" descriptor is optional only if the "syntax" is the universal syntax definition, otherwise the descriptor is required'; - }, - INVALID_DESCRIPTOR_TYPE(descriptorType: string) { - return `@property does not support descriptor of type "${descriptorType}"`; - }, - INVALID_DESCRIPTOR_NAME(descriptorName: string) { - return `@property does not support descriptor named "${descriptorName}"`; - }, + MISSING_REQUIRED_DESCRIPTOR: createDiagnosticReporter( + '01001', + 'error', + (descriptorName: string) => `@property rules require a "${descriptorName}" descriptor` + ), + MISSING_REQUIRED_INITIAL_VALUE_DESCRIPTOR: createDiagnosticReporter( + '01002', + 'warning', + () => + '@property "initial-value" descriptor is optional only if the "syntax" is the universal syntax definition, otherwise the descriptor is required' + ), + INVALID_DESCRIPTOR_TYPE: createDiagnosticReporter( + '01003', + 'error', + (descriptorType: string) => + `@property does not support descriptor of type "${descriptorType}"` + ), + INVALID_DESCRIPTOR_NAME: createDiagnosticReporter( + '01004', + 'error', + (descriptorName: string) => + `@property does not support descriptor named "${descriptorName}"` + ), }; export function validateAtProperty( @@ -40,10 +51,10 @@ export function validateAtProperty( for (const node of atRule.nodes) { if (node.type !== 'decl') { if (node.type === 'atrule' || node.type === 'rule') { - diagnostics.warn( - node, + diagnostics.report( atPropertyValidationWarnings.INVALID_DESCRIPTOR_TYPE(node.type), { + node, word: 'params' in node ? node.params : node.selector, } ); @@ -53,13 +64,10 @@ export function validateAtProperty( } if (!AT_PROPERTY_DISCRIPTOR_LIST.includes(node.prop)) { - diagnostics.warn( + diagnostics.report(atPropertyValidationWarnings.INVALID_DESCRIPTOR_NAME(node.prop), { node, - atPropertyValidationWarnings.INVALID_DESCRIPTOR_NAME(node.prop), - { - word: node.prop, - } - ); + word: node.prop, + }); continue; } @@ -68,11 +76,10 @@ export function validateAtProperty( } if (!atPropertyValues.has('syntax')) { - diagnostics.warn( - atRule, - atPropertyValidationWarnings.MISSING_REQUIRED_DESCRIPTOR('syntax'), - { word: name } - ); + diagnostics.report(atPropertyValidationWarnings.MISSING_REQUIRED_DESCRIPTOR('syntax'), { + node: atRule, + word: name, + }); return { valid: false, @@ -80,11 +87,10 @@ export function validateAtProperty( } if (!atPropertyValues.has('inherits')) { - diagnostics.warn( - atRule, - atPropertyValidationWarnings.MISSING_REQUIRED_DESCRIPTOR('inherits'), - { word: name } - ); + diagnostics.report(atPropertyValidationWarnings.MISSING_REQUIRED_DESCRIPTOR('inherits'), { + node: atRule, + word: name, + }); return { valid: false, @@ -95,10 +101,12 @@ export function validateAtProperty( !atPropertyValues.has('initial-value') && atPropertyValues.get('syntax') !== UNIVERSAL_SYNTAX_DEFINITION ) { - diagnostics.warn( - atRule, + diagnostics.report( atPropertyValidationWarnings.MISSING_REQUIRED_INITIAL_VALUE_DESCRIPTOR(), - { word: name } + { + node: atRule, + word: name, + } ); return { diff --git a/packages/core/src/helpers/custom-selector.ts b/packages/core/src/helpers/custom-selector.ts new file mode 100644 index 000000000..e0d95e66f --- /dev/null +++ b/packages/core/src/helpers/custom-selector.ts @@ -0,0 +1,132 @@ +import { + PseudoClass, + Selector, + SelectorList, + walk, + SelectorNode, + parseCssSelector, +} from '@tokey/css-selector-parser'; +import cloneDeep from 'lodash.clonedeep'; + +export type CustomSelectorMap = Record; +type InsertionIter = (progress: boolean) => boolean; +type UnknownReport = { type: 'unknown'; origin: string; unknown: string }; +type CircularReport = { type: 'circular'; path: readonly string[] }; +export type TransformCustomSelectorReport = UnknownReport | CircularReport; + +export function transformCustomSelectorMap( + customSelectors: CustomSelectorMap, + report: (data: TransformCustomSelectorReport) => void +) { + const result: CustomSelectorMap = {}; + const link = (name: string, path: string[]) => { + const ast = customSelectors[name]; + if (!ast) { + return; + } + result[name] = transformCustomSelectors( + ast, + (nestedName) => { + const selector = `:--${nestedName}`; + if (path.includes(selector)) { + // loop!: report & preserve source selector + report({ type: 'circular', path }); + return parseCssSelector(selector); + } + if (!result[nestedName]) { + link(nestedName, [...path, selector]); + } + return result[nestedName]; + }, + ({ type, unknown }) => report({ type, origin: name, unknown }) + ); + }; + for (const name of Object.keys(customSelectors)) { + link(name, [`:--${name}`]); + } + return result; +} + +function isCustomSelectorNode(node: SelectorNode): node is PseudoClass { + return node.type === 'pseudo_class' && node.value.startsWith('--'); +} + +export function transformCustomSelectors( + inputSelectors: SelectorList, + getCustomSelector: (name: string) => SelectorList | undefined, + report: (data: UnknownReport) => void +): SelectorList { + const result: SelectorList = []; + for (const selector of inputSelectors) { + result.push(...transformCustomSelector(selector, getCustomSelector, report)); + } + return result; +} + +function transformCustomSelector( + inputSelector: Selector, + getCustomSelector: (name: string) => SelectorList | undefined, + report: (data: UnknownReport) => void +): SelectorList { + const insertions: InsertionIter[] = []; + // get insertion points + walk(inputSelector, (node, index, _nodes, parents) => { + if (isCustomSelectorNode(node)) { + const name = node.value.slice(2); + const targetSelectors = getCustomSelector(name); + if (!targetSelectors) { + report({ type: 'unknown', origin: '', unknown: name }); + } else if (targetSelectors.length !== 0) { + const parent = parents[parents.length - 1]; + if (parent && 'nodes' in parent && parent.nodes) { + let selectorIndex = 0; + insertions.push((progress) => { + if (progress) { + selectorIndex++; + } + const overflow = selectorIndex === targetSelectors.length; + if (overflow) { + selectorIndex = 0; + } + const currentSelector = targetSelectors[selectorIndex]; + currentSelector.before = currentSelector.after = ''; + parent.nodes![index] = currentSelector; + return overflow; + }); + } + } + } + }); + // permute selectors + const output: SelectorList = []; + if (insertions.length) { + // save first permutation + insertions.forEach((updateSelector) => updateSelector(false)); + output.push(cloneDeep(inputSelector)); + // collect rest of permutations + let run = true; + while (run) { + let progressIdx = 0; + for (let i = 0; i < insertions.length; ++i) { + const updateSelector = insertions[i]; + const moveToNext = i === progressIdx; + const overflow = updateSelector(moveToNext); + if (overflow) { + if (progressIdx < insertions.length - 1) { + // advance next insertion point + progressIdx++; + } else { + // finish run over all permutations + run = false; + return output; + } + } else { + // no need to update any farther this round + break; + } + } + output.push(cloneDeep(inputSelector)); + } + } + return [inputSelector]; +} diff --git a/packages/core/src/helpers/custom-state.ts b/packages/core/src/helpers/custom-state.ts index 792970985..ae863029b 100644 --- a/packages/core/src/helpers/custom-state.ts +++ b/packages/core/src/helpers/custom-state.ts @@ -3,7 +3,7 @@ import type { Diagnostics } from '../diagnostics'; import { parseSelectorWithCache } from './selector'; import type { StylableMeta } from '../stylable-meta'; import type { StylableResolver } from '../stylable-resolver'; -import { validateStateArgument } from '../pseudo-states'; +import { validateStateArgument, stateDiagnostics } from '../pseudo-states'; import { CSSClass } from '../features'; export function validateRuleStateDefinition( @@ -38,14 +38,19 @@ export function validateRuleStateDefinition( !!state.defaultValue ); if (errors) { - errors.unshift( - `pseudo-state "${stateName}" default value "${state.defaultValue}" failed validation:` - ); rule.walkDecls((decl) => { if (decl.prop === `-st-states`) { - diagnostics.warn(decl, errors.join('\n'), { - word: decl.value, - }); + diagnostics.report( + stateDiagnostics.DEFAULT_PARAM_FAILS_VALIDATION( + stateName, + state.defaultValue || '', + errors + ), + { + node: decl, + word: decl.value, + } + ); return false; } return; diff --git a/packages/core/src/helpers/global.ts b/packages/core/src/helpers/global.ts index 00d476aee..73600339e 100644 --- a/packages/core/src/helpers/global.ts +++ b/packages/core/src/helpers/global.ts @@ -3,7 +3,7 @@ import type { FunctionNode } from 'postcss-value-parser'; export const PROPERTY = `-st-global` as const; export const GLOBAL_FUNC = 'st-global' as const; -export const globalValueRegExp = new RegExp(`^${GLOBAL_FUNC}\\((.*?)\\)$`); +const globalValueRegExp = new RegExp(`^${GLOBAL_FUNC}\\((.*?)\\)$`); export function globalValue(str: string) { const match = str.match(globalValueRegExp); diff --git a/packages/core/src/helpers/import.ts b/packages/core/src/helpers/import.ts index 642ec7f92..35367c86b 100644 --- a/packages/core/src/helpers/import.ts +++ b/packages/core/src/helpers/import.ts @@ -1,6 +1,6 @@ import path from 'path'; import { parseImports } from '@tokey/imports-parser'; -import { Diagnostics } from '../diagnostics'; +import { createDiagnosticReporter, Diagnostics } from '../diagnostics'; import type { Imported } from '../features'; import { Root, decl, Declaration, atRule, rule, Rule, AtRule } from 'postcss'; import { stripQuotation } from '../helpers/string'; @@ -15,51 +15,75 @@ import postcssValueParser, { } from 'postcss-value-parser'; export const parseImportMessages = { - ST_IMPORT_STAR() { - return '@st-import * is not supported'; - }, - INVALID_ST_IMPORT_FORMAT(errors: string[]) { - return `Invalid @st-import format:\n - ${errors.join('\n - ')}`; - }, - - ST_IMPORT_EMPTY_FROM() { - return '@st-import must specify a valid "from" string value'; - }, - EMPTY_IMPORT_FROM() { - return '"-st-from" cannot be empty'; - }, - - MULTIPLE_FROM_IN_IMPORT() { - return `cannot define multiple "-st-from" declarations in a single import`; - }, - DEFAULT_IMPORT_IS_LOWER_CASE() { - return 'Default import of a Stylable stylesheet must start with an upper-case letter'; - }, - ILLEGAL_PROP_IN_IMPORT(propName: string) { - return `"${propName}" css attribute cannot be used inside :import block`; - }, - FROM_PROP_MISSING_IN_IMPORT() { - return `"-st-from" is missing in :import block`; - }, - INVALID_NAMED_IMPORT_AS(name: string) { - return `Invalid named import "as" with name "${name}"`; - }, - INVALID_NESTED_TYPED_IMPORT(type: string, name: string) { - return `Invalid nested ${type} import "${name}"`; - }, + ST_IMPORT_STAR: createDiagnosticReporter( + '05001', + 'error', + () => '@st-import * is not supported' + ), + INVALID_ST_IMPORT_FORMAT: createDiagnosticReporter( + '05002', + 'error', + (errors: string[]) => `Invalid @st-import format:\n - ${errors.join('\n - ')}` + ), + ST_IMPORT_EMPTY_FROM: createDiagnosticReporter( + '05003', + 'error', + () => '@st-import must specify a valid "from" string value' + ), + EMPTY_IMPORT_FROM: createDiagnosticReporter( + '05004', + 'error', + () => '"-st-from" cannot be empty' + ), + MULTIPLE_FROM_IN_IMPORT: createDiagnosticReporter( + '05005', + 'warning', + () => `cannot define multiple "-st-from" declarations in a single import` + ), + DEFAULT_IMPORT_IS_LOWER_CASE: createDiagnosticReporter( + '05006', + 'warning', + () => 'Default import of a Stylable stylesheet must start with an upper-case letter' + ), + ILLEGAL_PROP_IN_IMPORT: createDiagnosticReporter( + '05007', + 'warning', + (propName: string) => `"${propName}" css attribute cannot be used inside :import block` + ), + FROM_PROP_MISSING_IN_IMPORT: createDiagnosticReporter( + '05008', + 'error', + () => `"-st-from" is missing in :import block` + ), + INVALID_NAMED_IMPORT_AS: createDiagnosticReporter( + '05009', + 'error', + (name: string) => `Invalid named import "as" with name "${name}"` + ), + INVALID_NESTED_KEYFRAMES: createDiagnosticReporter( + '05010', + 'error', + (name: string) => `Invalid nested keyframes import "${name}"` + ), + INVALID_NESTED_TYPED_IMPORT: createDiagnosticReporter( + '05019', + 'warning', + (type: string, name: string) => `Invalid nested ${type} import "${name}"` + ), }; export const ensureImportsMessages = { - ATTEMPT_OVERRIDE_SYMBOL( - kind: 'default' | 'named' | 'keyframes', - origin: string, - override: string - ) { - return `Attempt to override existing ${kind} import symbol. ${origin} -> ${override}`; - }, - PATCH_CONTAINS_NEW_IMPORT_IN_NEW_IMPORT_NONE_MODE() { - return `Attempt to insert new a import in newImport "none" mode`; - }, + ATTEMPT_OVERRIDE_SYMBOL: createDiagnosticReporter( + '16001', + 'error', + (kind: 'default' | 'named' | 'keyframes', origin: string, override: string) => + `Attempt to override existing ${kind} import symbol. ${origin} -> ${override}` + ), + PATCH_CONTAINS_NEW_IMPORT_IN_NEW_IMPORT_NONE_MODE: createDiagnosticReporter( + '16002', + 'error', + () => `Attempt to insert new a import in newImport "none" mode` + ), }; export function createAtImportProps( @@ -146,9 +170,9 @@ function createImportPatches( } if (newImport === 'none') { if (handled.size !== importPatches.length) { - diagnostics.error( - ast, - ensureImportsMessages.PATCH_CONTAINS_NEW_IMPORT_IN_NEW_IMPORT_NONE_MODE() + diagnostics.report( + ensureImportsMessages.PATCH_CONTAINS_NEW_IMPORT_IN_NEW_IMPORT_NONE_MODE(), + { node: ast } ); } return patches; @@ -227,7 +251,7 @@ export function parseStImport(atRule: AtRule, context: string, diagnostics: Diag const imports = parseImports(`import ${atRule.params}`, '[', ']', true)[0]; if (imports && imports.star) { - diagnostics.error(atRule, parseImportMessages.ST_IMPORT_STAR()); + diagnostics.report(parseImportMessages.ST_IMPORT_STAR(), { node: atRule }); } else { setImportObjectFrom(imports.from || '', context, importObj); @@ -237,7 +261,8 @@ export function parseStImport(atRule: AtRule, context: string, diagnostics: Diag !isCompRoot(importObj.defaultExport) && importObj.from.endsWith(`.css`) ) { - diagnostics.warn(atRule, parseImportMessages.DEFAULT_IMPORT_IS_LOWER_CASE(), { + diagnostics.report(parseImportMessages.DEFAULT_IMPORT_IS_LOWER_CASE(), { + node: atRule, word: importObj.defaultExport, }); } @@ -255,9 +280,11 @@ export function parseStImport(atRule: AtRule, context: string, diagnostics: Diag } } if (imports.errors.length) { - diagnostics.error(atRule, parseImportMessages.INVALID_ST_IMPORT_FORMAT(imports.errors)); + diagnostics.report(parseImportMessages.INVALID_ST_IMPORT_FORMAT(imports.errors), { + node: atRule, + }); } else if (!imports.from?.trim()) { - diagnostics.error(atRule, parseImportMessages.ST_IMPORT_EMPTY_FROM()); + diagnostics.report(parseImportMessages.ST_IMPORT_EMPTY_FROM(), { node: atRule }); } } @@ -285,11 +312,13 @@ export function parsePseudoImport(rule: Rule, context: string, diagnostics: Diag case `-st-from`: { const importPath = stripQuotation(decl.value); if (!importPath.trim()) { - diagnostics.error(decl, parseImportMessages.EMPTY_IMPORT_FROM()); + diagnostics.report(parseImportMessages.EMPTY_IMPORT_FROM(), { node: decl }); } if (fromExists) { - diagnostics.warn(rule, parseImportMessages.MULTIPLE_FROM_IN_IMPORT()); + diagnostics.report(parseImportMessages.MULTIPLE_FROM_IN_IMPORT(), { + node: rule, + }); } setImportObjectFrom(importPath, context, importObj); @@ -299,7 +328,8 @@ export function parsePseudoImport(rule: Rule, context: string, diagnostics: Diag case `-st-default`: importObj.defaultExport = decl.value; if (!isCompRoot(importObj.defaultExport) && importObj.from.endsWith(`.css`)) { - diagnostics.warn(decl, parseImportMessages.DEFAULT_IMPORT_IS_LOWER_CASE(), { + diagnostics.report(parseImportMessages.DEFAULT_IMPORT_IS_LOWER_CASE(), { + node: decl, word: importObj.defaultExport, }); } @@ -317,7 +347,8 @@ export function parsePseudoImport(rule: Rule, context: string, diagnostics: Diag } break; default: - diagnostics.warn(decl, parseImportMessages.ILLEGAL_PROP_IN_IMPORT(decl.prop), { + diagnostics.report(parseImportMessages.ILLEGAL_PROP_IN_IMPORT(decl.prop), { + node: decl, word: decl.prop, }); break; @@ -325,7 +356,9 @@ export function parsePseudoImport(rule: Rule, context: string, diagnostics: Diag }); if (!importObj.from) { - diagnostics.error(rule, parseImportMessages.FROM_PROP_MISSING_IN_IMPORT()); + diagnostics.report(parseImportMessages.FROM_PROP_MISSING_IN_IMPORT(), { + node: rule, + }); } return importObj; } @@ -458,15 +491,17 @@ function processImports( if (currentSymbol === symbol) { continue; } else if (currentSymbol) { - diagnostics.error( - imported.rule, + diagnostics.report( ensureImportsMessages.ATTEMPT_OVERRIDE_SYMBOL( op, currentSymbol === asName ? currentSymbol : `${currentSymbol} as ${asName}`, symbol === asName ? symbol : `${symbol} as ${asName}` - ) + ), + { + node: imported.rule, + } ); } else { imported[op][asName] = symbol; @@ -478,13 +513,15 @@ function processImports( if (!imported.defaultExport) { imported.defaultExport = patch.defaultExport; } else if (imported.defaultExport !== patch.defaultExport) { - diagnostics.error( - imported.rule, + diagnostics.report( ensureImportsMessages.ATTEMPT_OVERRIDE_SYMBOL( 'default', imported.defaultExport, patch.defaultExport - ) + ), + { + node: imported.rule, + } ); } } @@ -514,10 +551,9 @@ function handleNamedTokens( i += 4; //ignore next 4 tokens } else { i += !asName ? 3 : 2; - diagnostics.warn( + diagnostics.report(parseImportMessages.INVALID_NAMED_IMPORT_AS(token.value), { node, - parseImportMessages.INVALID_NAMED_IMPORT_AS(token.value) - ); + }); continue; } } else { @@ -525,12 +561,12 @@ function handleNamedTokens( } } else if (token.type === 'function') { if (!typedBuckets) { - diagnostics.warn( - node, + diagnostics.report( parseImportMessages.INVALID_NESTED_TYPED_IMPORT( token.value, postcssValueParser.stringify(token) - ) + ), + { node } ); } else { typedBuckets[token.value] ??= {}; diff --git a/packages/core/src/helpers/mixin.ts b/packages/core/src/helpers/mixin.ts index 7ea1a5e2f..f879008d8 100644 --- a/packages/core/src/helpers/mixin.ts +++ b/packages/core/src/helpers/mixin.ts @@ -1,28 +1,30 @@ -import type { Diagnostics } from '../diagnostics'; +import { createDiagnosticReporter, DiagnosticBase, Diagnostics } from '../diagnostics'; import { strategies, valueDiagnostics } from './value'; import type { MixinValue } from '../features'; import type * as postcss from 'postcss'; import postcssValueParser from 'postcss-value-parser'; -export const diagnostics = { +export const mixinHelperDiagnostics = { INVALID_NAMED_PARAMS: valueDiagnostics.INVALID_NAMED_PARAMS, - VALUE_CANNOT_BE_STRING() { - return 'value can not be a string (remove quotes?)'; - }, + VALUE_CANNOT_BE_STRING: createDiagnosticReporter( + '10008', + 'error', + () => 'value can not be a string (remove quotes?)' + ), }; export function parseStMixin( mixinNode: postcss.Declaration, strategy: (type: string) => 'named' | 'args', - report?: Diagnostics, + diagnostics?: Diagnostics, emitStrategyDiagnostics = true ) { const ast = postcssValueParser(mixinNode.value); const mixins: Array = []; - function reportWarning(message: string, options?: { word: string }) { + function reportWarning(diagnostic: DiagnosticBase, options?: { word: string }) { if (emitStrategyDiagnostics) { - report?.warn(mixinNode, message, options); + diagnostics?.report(diagnostic, { ...options, node: mixinNode }); } } @@ -42,7 +44,8 @@ export function parseStMixin( originDecl: mixinNode, }); } else if (node.type === 'string') { - report?.error(mixinNode, diagnostics.VALUE_CANNOT_BE_STRING(), { + diagnostics?.report(mixinHelperDiagnostics.VALUE_CANNOT_BE_STRING(), { + node: mixinNode, word: mixinNode.value, }); } diff --git a/packages/core/src/helpers/namespace.ts b/packages/core/src/helpers/namespace.ts index 34f363fbb..4839d36ef 100644 --- a/packages/core/src/helpers/namespace.ts +++ b/packages/core/src/helpers/namespace.ts @@ -1,7 +1,8 @@ import { murmurhash3_32_gc } from '../murmurhash'; -export function namespace(name: string, namespace: string, delimiter = '__') { - return namespace ? namespace + delimiter + name : name; +export const namespaceDelimiter = '__'; +export function namespace(name: string, namespace: string) { + return namespace ? namespace + namespaceDelimiter + name : name; } export interface PackageInfo { diff --git a/packages/core/src/helpers/rule.ts b/packages/core/src/helpers/rule.ts index a2094344b..642f63825 100644 --- a/packages/core/src/helpers/rule.ts +++ b/packages/core/src/helpers/rule.ts @@ -11,10 +11,10 @@ import { Selector, ImmutableSelectorNode, groupCompoundSelectors, + SelectorList, } from '@tokey/css-selector-parser'; import * as postcss from 'postcss'; -import { ignoreDeprecationWarn } from './deprecation'; -import type { SRule } from '../deprecated/postcss-ast-extension'; +import { transformCustomSelectors } from './custom-selector'; export function isChildOfAtRule(rule: postcss.Container, atRuleName: string) { return !!( @@ -77,21 +77,33 @@ export function createSubsetAst( root: postcss.Root | postcss.AtRule, selectorPrefix: string, mixinTarget?: T, - isRoot = false + isRoot = false, + getCustomSelector?: (name: string) => SelectorList | undefined, + scopeSelector = '' ): T { // keyframes on class mixin? const prefixSelectorList = parseSelectorWithCache(selectorPrefix); const prefixType = prefixSelectorList[0].nodes[0]; const containsPrefix = containsMatchInFirstChunk.bind(null, prefixType); const mixinRoot = mixinTarget ? mixinTarget : postcss.root(); - + const scopeSelectorAST = parseSelectorWithCache(scopeSelector); root.nodes.forEach((node) => { - if (node.type === `rule`) { - const selectorAst = parseSelectorWithCache(node.selector, { clone: true }); - const ast = isRoot + if (node.type === `rule` && (node.selector === ':vars' || node.selector === ':import')) { + // nodes that don't mix + return; + } else if (node.type === `rule`) { + let selectorAst = parseSelectorWithCache(node.selector, { clone: true }); + if (scopeSelector) { + selectorAst = scopeNestedSelector(scopeSelectorAST, selectorAst, isRoot).ast; + } + let ast = isRoot ? scopeNestedSelector(prefixSelectorList, selectorAst, true).ast : selectorAst; - + if (getCustomSelector) { + ast = transformCustomSelectors(ast, getCustomSelector, () => { + /*don't report*/ + }); + } const matchesSelectors = isRoot ? ast : ast.filter((node) => containsPrefix(node)); if (matchesSelectors.length) { @@ -108,7 +120,13 @@ export function createSubsetAst( mixinRoot.append(node.clone({ selector })); } } else if (node.type === `atrule`) { - if (node.name === 'media' || node.name === 'supports' || node.name === 'layer') { + if ( + node.name === 'media' || + node.name === 'supports' || + node.name === 'st-scope' || + node.name === 'layer' + ) { + const scopeSelector = node.name === 'st-scope' ? node.params : ''; const atRuleSubset = createSubsetAst( node, selectorPrefix, @@ -116,7 +134,9 @@ export function createSubsetAst( params: node.params, name: node.name, }), - isRoot + isRoot, + getCustomSelector, + scopeSelector ); if (atRuleSubset.nodes) { mixinRoot.append(atRuleSubset); @@ -215,7 +235,3 @@ export function findRule( }); return found; } - -export function getRuleScopeSelector(rule: postcss.Rule) { - return ignoreDeprecationWarn(() => (rule as SRule).stScopeSelector); -} diff --git a/packages/core/src/helpers/value.ts b/packages/core/src/helpers/value.ts index 163d72438..11db0fe6a 100644 --- a/packages/core/src/helpers/value.ts +++ b/packages/core/src/helpers/value.ts @@ -1,8 +1,25 @@ import type { ParsedValue } from '../types'; import postcssValueParser from 'postcss-value-parser'; import type { Node as ValueNode } from 'postcss-value-parser'; +import { createDiagnosticReporter, DiagnosticBase } from '../diagnostics'; -export type ReportWarning = (message: string, options?: { word: string }) => void; +export type ReportWarning = (diagnostic: DiagnosticBase, options?: { word: string }) => void; + +export const valueDiagnostics = { + INVALID_NAMED_PARAMS: createDiagnosticReporter( + '13001', + 'error', + () => `invalid named parameters (e.g. "func(name value, [name value, ...])")` + ), + MISSING_REQUIRED_FORMATTER_ARG: createDiagnosticReporter( + '13002', + 'error', + (node: ParsedValue, argIndex: number) => + `${postcssValueParser.stringify( + node as postcssValueParser.Node + )}: argument at index ${argIndex} is empty` + ), +}; export function getNamedArgs(node: ParsedValue) { const args: ParsedValue[][] = []; @@ -73,11 +90,7 @@ export function getFormatterArgs( function checkEmptyArg() { if (reportWarning && argsResult.length && currentArg.trim() === '') { - reportWarning( - `${postcssValueParser.stringify( - node as postcssValueParser.Node - )}: argument at index ${argIndex} is empty` - ); + reportWarning(valueDiagnostics.MISSING_REQUIRED_FORMATTER_ARG(node, argIndex)); } } } @@ -149,11 +162,6 @@ export function validateAllowedNodesUntil( return true; } -export const valueDiagnostics = { - INVALID_NAMED_PARAMS: () => - `invalid named parameters (e.g. "func(name value, [name value, ...])")`, -}; - export const strategies = { named: (node: any, reportWarning?: ReportWarning) => { const named: Record = {}; diff --git a/packages/core/src/index-deprecated.ts b/packages/core/src/index-deprecated.ts deleted file mode 100644 index de74b6151..000000000 --- a/packages/core/src/index-deprecated.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { wrapFunctionForDeprecation } from './helpers/deprecation'; -export { CssParser, cssObjectToAst, cssParse } from './parser'; -import { safeParse as deprecatedSafeParse } from './parser'; -/**@deprecated*/ -export const safeParse = wrapFunctionForDeprecation(deprecatedSafeParse, { - name: `safeParse`, - pleaseUse: `postcss-safe-parser`, -}); -export { CacheItem, FileProcessor, cachedProcessFile, processFn } from './cached-process-file'; -export type { StylableDirectives, MappedStates } from './features'; -export { reservedKeyFrames } from './features/css-keyframes'; -import { scopeCSSVar as scopeCSSVarDeprecated } from './features/css-custom-property'; -/**@deprecated*/ -export const scopeCSSVar = wrapFunctionForDeprecation(scopeCSSVarDeprecated, { - name: `scopeCSSVar`, - pleaseUse: `stylable.transformCustomProperty`, -}); -export { - StylableProcessor, - createEmptyMeta, - processorWarnings, - validateScopingSelector, -} from './stylable-processor'; -import { process as deprecatedProcess } from './stylable-processor'; -/**@deprecated*/ -export const process = wrapFunctionForDeprecation(deprecatedProcess, { - name: `process`, - pleaseUse: `stylable.analyze`, -}); -import { ensureModuleImport, parseModuleImportStatement } from './helpers/import'; -/**@deprecated*/ -export const parseStylableImport = wrapFunctionForDeprecation(parseModuleImportStatement, { - name: `parseStylableImport`, - pleaseUse: `import { parseModuleImportStatement } from '@stylable/core'`, -}); -/**@deprecated*/ -export const ensureStylableImports = wrapFunctionForDeprecation(ensureModuleImport, { - name: `ensureStylableImports`, - pleaseUse: `import { ensureModuleImport } from '@stylable/core'`, -}); -export { generateScopedCSSVar } from './helpers/css-custom-property'; -import { validateCustomPropertyName } from './helpers/css-custom-property'; -/**@deprecated*/ -export const isCSSVarProp = wrapFunctionForDeprecation(validateCustomPropertyName, { - name: `isCSSVarProp`, - pleaseUse: `import { validateCustomPropertyName } from '@stylable/core'`, -}); -export { globalValueRegExp } from './helpers/global'; -import { GLOBAL_FUNC } from './helpers/global'; -/**@deprecated*/ -export const paramMapping = { - global: GLOBAL_FUNC, -}; -export { createDefaultResolver } from './module-resolver'; -export { RESERVED_ROOT_NAME } from './stylable-meta'; -export { - KeyFrameWithNode, - ResolvedElement, - StylableExports, - /** @deprecated use stylable.transform / stylable.transformSelector... */ - StylableTransformer, - TransformHooks, - TransformerOptions, - postProcessor, - replaceValueHook, - transformerWarnings, -} from './stylable-transformer'; -export { - CUSTOM_SELECTOR_RE, - expandCustomSelectors, - findDeclaration, - getAlias, - getSourcePath, - isValidClassName, - isValidDeclaration, - mergeRules, - transformMatchesOnRule, -} from './stylable-utils'; -export { JsModule, StylableResolverCache, isInPath, StylableResolver } from './stylable-resolver'; -export { DiagnosticOptions } from './diagnostics'; -export { File, MinimalFSSetup } from './deprecated/memory-minimal-fs'; -import { createMinimalFS as createMinimalFSDeprecated } from './deprecated/memory-minimal-fs'; -/**@deprecated*/ -export const createMinimalFS = wrapFunctionForDeprecation(createMinimalFSDeprecated, { - name: `createMinimalFS`, -}); -export { ArgValue, ExtendsValue, SBTypesParsers } from './stylable-value-parsers'; -export { - valueMapping, - rootValueMapping, - stKeys, - stValues, - stValuesMap, - STYLABLE_NAMED_MATCHER, - mixinDeclRegExp, - animationPropRegExp, - STYLABLE_VALUE_MATCHER, -} from './deprecated/value-mapping'; -export { TypedClass } from './deprecated/leftovers'; -export { createStylableFileProcessor } from './create-stylable-processor'; -export { CreateProcessorOptions } from './stylable'; -export { - CSSObject, - IStylableClassNameOptimizer, - IStylableNamespaceOptimizer, - IStylableOptimizer, - ModuleResolver, - OptimizeConfig, - PartialObject, - StateArguments, - StateParsedValue, - StateTypeValidator, -} from './types'; -import type { ParsedValue as DeprecatedParsedValue } from './types'; -/**@deprecated*/ -export type ParsedValue = DeprecatedParsedValue; -export { - OnUrlCallback, - UrlNode, - assureRelativeUrlPrefix, - collectAssets, - fixRelativeUrls, - isAsset, - isExternal, - isUrl, - makeAbsolute, -} from './stylable-assets'; -export { - ResolvedFormatter, - ValueFormatter, - functionWarnings, - resolveArgumentsValue, -} from './functions'; -import { evalDeclarationValue as evalDeclarationValueDeprecated } from './functions'; -/**@deprecated*/ -export const evalDeclarationValue = wrapFunctionForDeprecation(evalDeclarationValueDeprecated, { - name: `evalDeclarationValue`, - pleaseUse: `stylable.transformDecl`, -}); -export { - Box, - BoxedValueArray, - BoxedValueMap, - CustomValueExtension, - CustomValueStrategy, - JSValueExtension, - box, - createCustomValue, - getBoxValue, - isCustomValue, - stTypes, - unbox, -} from './custom-values'; -export { StateParamType, StateResult, SubValidator, systemValidators } from './state-validators'; -export { - isCssNativeFunction, - nativeFunctions, - nativeFunctionsDic, - nativePseudoClasses, - nativePseudoElements, -} from './native-reserved-lists'; -export { noCollisionNamespace, packageNamespaceFactory } from './resolve-namespace-factories'; -export { DiagnosticsMode, EmitDiagnosticsContext, emitDiagnostics } from './report-diagnostic'; -import { visitMetaCSSDependenciesBFS as deprecatedVisitMetaCSSDependenciesBFS } from './visit-meta-css-dependencies'; -/**@deprecated use Stylable.getDependencies in v5*/ -export const visitMetaCSSDependenciesBFS = deprecatedVisitMetaCSSDependenciesBFS; -export { murmurhash3_32_gc } from './murmurhash'; -import { - booleanStateDelimiter, - createBooleanStateClassName, - createStateWithParamClassName, - processPseudoStates, - resolveStateParam, - setStateToNode, - stateErrors, - stateMiddleDelimiter, - stateWithParamDelimiter, - validateStateArgument, - validateStateDefinition, -} from './pseudo-states'; -export const pseudoStates = { - booleanStateDelimiter, - createBooleanStateClassName, - createStateWithParamClassName, - processPseudoStates, - resolveStateParam, - setStateToNode, - stateErrors, - stateMiddleDelimiter, - stateWithParamDelimiter, - validateStateArgument, - validateStateDefinition, -}; -export { getRuleScopeSelector } from './helpers/rule'; -export { - getFormatterArgs, - getNamedArgs, - getStringValue, - groupValues, - listOptions, - validateAllowedNodesUntil, - strategies, - ReportWarning, -} from './helpers/value'; - -// *** deprecated *** - -import { isCompRoot as deprecatedIsCompRoot } from './helpers/selector'; -/**@deprecated*/ -export const isCompRoot = wrapFunctionForDeprecation(deprecatedIsCompRoot, { - name: `isCompRoot`, -}); - -import { - isChildOfAtRule as deprecatedIsChildOfAtRule, - createWarningRule as deprecatedCreateWarningRule, -} from './helpers/rule'; -/**@deprecated*/ -export const isChildOfAtRule = wrapFunctionForDeprecation(deprecatedIsChildOfAtRule, { - name: `isChildOfAtRule`, -}); -/**@deprecated*/ -export const createWarningRule = wrapFunctionForDeprecation(deprecatedCreateWarningRule, { - name: `createWarningRule`, -}); - -import { getOriginDefinition as deprecatedGetOriginDefinition } from './helpers/resolve'; -/**@deprecated*/ -export const getOriginDefinition = wrapFunctionForDeprecation(deprecatedGetOriginDefinition, { - name: `getOriginDefinition`, -}); - -export type { SRule, SDecl, DeclStylableProps } from './deprecated/postcss-ast-extension'; -import { getDeclStylable as deprecatedGetDeclStylable } from './deprecated/postcss-ast-extension'; -/**@deprecated*/ -export const getDeclStylable = wrapFunctionForDeprecation(deprecatedGetDeclStylable, { - name: `getDeclStylable`, -}); - -import { - scopeSelector as deprecatedScopeSelector, - createSubsetAst as deprecatedCreateSubsetAst, // report deprecation - removeUnusedRules as deprecatedRemoveUnusedRules, - findRule as deprecatedFindRule, -} from './deprecated/deprecated-stylable-utils'; -/**@deprecated*/ -export const scopeSelector = wrapFunctionForDeprecation(deprecatedScopeSelector, { - name: `scopeSelector`, -}); -/**@deprecated*/ -export const createSubsetAst = deprecatedCreateSubsetAst; -/**@deprecated*/ -export const removeUnusedRules = wrapFunctionForDeprecation(deprecatedRemoveUnusedRules, { - name: `removeUnusedRules`, -}); -/**@deprecated*/ -export const findRule = wrapFunctionForDeprecation(deprecatedFindRule, { - name: `findRule`, -}); - -export type { - SelectorChunk, - SelectorChunk2, - Visitor, - SelectorAstNode, - PseudoSelectorAstNode, -} from './deprecated/deprecated-selector-utils'; -import { - matchSelectorTarget as deprecatedMatchSelectorTarget, - fixChunkOrdering as deprecatedFixChunkOrdering, - filterChunkNodesByType as deprecatedFilterChunkNodesByType, - separateChunks as deprecatedSeparateChunks, - separateChunks2 as deprecatedSeparateChunks2, - mergeChunks as deprecatedMergeChunks, - matchAtMedia as deprecatedMatchAtMedia, - matchAtKeyframes as deprecatedMatchAtKeyframes, - isImport as deprecatedIsImport, - isSimpleSelector as deprecatedIsSimpleSelector, - isGlobal as deprecatedIsGlobal, - createChecker as deprecatedCreateChecker, - isNested as deprecatedIsNested, - traverseNode as deprecatedTraverseNode, - parseSelector as deprecatedParseSelector, - stringifySelector as deprecatedStringifySelector, - isNodeMatch as deprecatedIsNodeMatch, - createSimpleSelectorChecker as deprecatedCreateSimpleSelectorChecker, -} from './deprecated/deprecated-selector-utils'; -/**@deprecated*/ -export const matchSelectorTarget = deprecatedMatchSelectorTarget; -/**@deprecated*/ -export const fixChunkOrdering = wrapFunctionForDeprecation(deprecatedFixChunkOrdering, { - name: `fixChunkOrdering`, -}); -/**@deprecated*/ -export const filterChunkNodesByType = wrapFunctionForDeprecation(deprecatedFilterChunkNodesByType, { - name: `filterChunkNodesByType`, -}); -/**@deprecated*/ -export const separateChunks = wrapFunctionForDeprecation(deprecatedSeparateChunks, { - name: `separateChunks`, -}); -/**@deprecated*/ -export const separateChunks2 = wrapFunctionForDeprecation(deprecatedSeparateChunks2, { - name: `separateChunks2`, -}); -/**@deprecated*/ -export const mergeChunks = wrapFunctionForDeprecation(deprecatedMergeChunks, { - name: `mergeChunks`, -}); -/**@deprecated*/ -export const matchAtMedia = wrapFunctionForDeprecation(deprecatedMatchAtMedia, { - name: `matchAtMedia`, -}); -/**@deprecated*/ -export const matchAtKeyframes = wrapFunctionForDeprecation(deprecatedMatchAtKeyframes, { - name: `matchAtKeyframes`, -}); -/**@deprecated*/ -export const isImport = wrapFunctionForDeprecation(deprecatedIsImport, { - name: `isImport`, -}); -/**@deprecated*/ -export const isSimpleSelector = wrapFunctionForDeprecation(deprecatedIsSimpleSelector, { - name: `isSimpleSelector`, -}); -/**@deprecated*/ -export const isRootValid = wrapFunctionForDeprecation(() => true, { - name: `isRootValid`, -}); -/**@deprecated*/ -export const isGlobal = wrapFunctionForDeprecation(deprecatedIsGlobal, { - name: `isGlobal`, -}); -/**@deprecated*/ -export const createChecker = wrapFunctionForDeprecation(deprecatedCreateChecker, { - name: `createChecker`, -}); -/**@deprecated*/ -export const isNested = wrapFunctionForDeprecation(deprecatedIsNested, { - name: `isNested`, -}); -/**@deprecated*/ -export const isNodeMatch = wrapFunctionForDeprecation(deprecatedIsNodeMatch, { - name: `isNodeMatch`, -}); -/**@deprecated*/ -export const traverseNode = wrapFunctionForDeprecation(deprecatedTraverseNode, { - name: `traverseNode`, - pleaseUse: `"import { walk } from '@tokey/css-selector-parser'"`, -}); -/**@deprecated*/ -export const parseSelector = wrapFunctionForDeprecation(deprecatedParseSelector, { - name: `parseSelector`, - pleaseUse: `"import { parseCssSelector } from '@tokey/css-selector-parser'"`, -}); -/**@deprecated*/ -export const stringifySelector = wrapFunctionForDeprecation(deprecatedStringifySelector, { - name: `stringifySelector`, - pleaseUse: `"import { stringifySelector } from '@tokey/css-selector-parser'"`, -}); -/**@deprecated*/ -export const createSimpleSelectorChecker = wrapFunctionForDeprecation( - deprecatedCreateSimpleSelectorChecker, - { - name: `createSimpleSelectorChecker`, - } -); diff --git a/packages/core/src/index-internal.ts b/packages/core/src/index-internal.ts index f4fd88f7d..af99b3417 100644 --- a/packages/core/src/index-internal.ts +++ b/packages/core/src/index-internal.ts @@ -1,6 +1,50 @@ export { safeParse } from './parser'; +export { processorDiagnostics, StylableProcessor } from './stylable-processor'; +export { + StylableTransformer, + postProcessor, + replaceValueHook, + StylableExports, + transformerDiagnostics, + ResolvedElement, +} from './stylable-transformer'; +export { MappedStates, STCustomSelector } from './features'; +export { murmurhash3_32_gc } from './murmurhash'; +export { cssParse } from './parser'; +export type { OptimizeConfig, IStylableOptimizer, StateParsedValue } from './types'; +export { + nativePseudoClasses, + nativePseudoElements, + knownPseudoClassesWithNestedSelectors, +} from './native-reserved-lists'; +export { isAsset, makeAbsolute } from './stylable-assets'; +export { namespace, namespaceDelimiter } from './helpers/namespace'; +export { + emitDiagnostics, + DiagnosticsMode, + EmitDiagnosticsContext, + reportDiagnostic, +} from './report-diagnostic'; +export { StylableResolver, StylableResolverCache } from './stylable-resolver'; +export { CacheItem, FileProcessor, cachedProcessFile, processFn } from './cached-process-file'; +export { createStylableFileProcessor } from './create-stylable-processor'; +export { packageNamespaceFactory } from './resolve-namespace-factories'; +import { + createBooleanStateClassName, + createStateWithParamClassName, + resolveStateParam, + stateMiddleDelimiter, + booleanStateDelimiter, +} from './pseudo-states'; +export const pseudoStates = { + createBooleanStateClassName, + createStateWithParamClassName, + resolveStateParam, + stateMiddleDelimiter, + booleanStateDelimiter, +}; +export { BoxedValueArray, BoxedValueMap, createCustomValue } from './custom-values'; +export { systemValidators } from './state-validators'; +export { DiagnosticBase } from './diagnostics'; export { getAstNodeAt } from './helpers/ast'; -export { process } from './stylable-processor'; -export { StylableTransformer } from './stylable-transformer'; export { tryCollectImportsDeep } from './helpers/import'; -export type { MappedStates } from './features'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ff6f61821..db5188b96 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,5 +1,5 @@ export { Stylable, StylableConfig } from './stylable'; -export { Diagnostics, Diagnostic, DiagnosticType } from './diagnostics'; +export { Diagnostics, Diagnostic, DiagnosticSeverity } from './diagnostics'; export type { StylableSymbol, ClassSymbol, @@ -9,25 +9,18 @@ export type { VarSymbol, Imported, KeyframesSymbol, - RefedMixin, - MixinValue, + MixinReflection, ComputedStVar, FlatComputedStVar, } from './features'; export { StylableMeta } from './stylable-meta'; export type { CSSResolve, JSResolve } from './stylable-resolver'; +export type { CSSDependency, JSDependency, Dependency } from './visit-meta-css-dependencies'; export type { StylableResults, RuntimeStVar } from './stylable-transformer'; // utils export type { MinimalFS } from './cached-process-file'; export { noCollisionNamespace } from './resolve-namespace-factories'; -export { processNamespace } from './stylable-processor'; - -// low-level api -export { parseModuleImportStatement, ensureModuleImport } from './helpers/import'; -export { validateCustomPropertyName } from './helpers/css-custom-property'; - -// namespace helpers export { createNamespaceStrategy, defaultNoMatchHandler, @@ -37,6 +30,10 @@ export { NamespaceBuilderParams, PackageInfo, } from './helpers/namespace'; +export { processNamespace } from './stylable-processor'; +export { CustomValueStrategy, createCustomValue } from './custom-values'; +export { createDefaultResolver } from './module-resolver'; -// deprecations -export * from './index-deprecated'; +// low-level api +export { parseModuleImportStatement, ensureModuleImport } from './helpers/import'; +export { validateCustomPropertyName } from './helpers/css-custom-property'; diff --git a/packages/core/src/parser.ts b/packages/core/src/parser.ts index 30a6f3974..6adfaed69 100644 --- a/packages/core/src/parser.ts +++ b/packages/core/src/parser.ts @@ -2,7 +2,8 @@ import postcss, { ProcessOptions, Root, parse as cssParse } from 'postcss'; import postcssNested from 'postcss-nested'; import postcssJS from 'postcss-js'; import safeParser from 'postcss-safe-parser'; -import type { CSSObject } from './types'; + +export type CSSObject = any & object; const processor = postcss([postcssNested()]); diff --git a/packages/core/src/pseudo-states.ts b/packages/core/src/pseudo-states.ts index 470a749e4..94436f365 100644 --- a/packages/core/src/pseudo-states.ts +++ b/packages/core/src/pseudo-states.ts @@ -1,21 +1,15 @@ import type * as postcss from 'postcss'; import postcssValueParser from 'postcss-value-parser'; -import type { Diagnostics } from './diagnostics'; +import { createDiagnosticReporter, Diagnostics } from './diagnostics'; import { evalDeclarationValue } from './functions'; -import { - parseSelectorWithCache, - convertToClass, - stringifySelector, - convertToInvalid, -} from './helpers/selector'; -import { wrapFunctionForDeprecation } from './helpers/deprecation'; +import { convertToClass, stringifySelector, convertToInvalid } from './helpers/selector'; import { groupValues, listOptions } from './helpers/value'; import type { PseudoClass } from '@tokey/css-selector-parser'; import { StateResult, systemValidators } from './state-validators'; import type { StylableMeta } from './stylable-meta'; import type { StylableResolver } from './stylable-resolver'; import type { ParsedValue, StateParsedValue } from './types'; -import { CSSClass, MappedStates } from './features'; +import type { MappedStates } from './features'; import { stripQuotation } from './helpers/string'; import { reservedFunctionalPseudoClasses } from './native-reserved-lists'; import cssesc from 'cssesc'; @@ -24,23 +18,72 @@ export const stateMiddleDelimiter = '-'; export const booleanStateDelimiter = '--'; export const stateWithParamDelimiter = booleanStateDelimiter + stateMiddleDelimiter; -export const stateErrors = { - UNKNOWN_STATE_USAGE: (name: string) => `unknown pseudo-state "${name}"`, - UNKNOWN_STATE_TYPE: (name: string, type: string) => - `pseudo-state "${name}" defined with unknown type: "${type}"`, - TOO_MANY_STATE_TYPES: (name: string, types: string[]) => - `pseudo-state "${name}(${types.join(', ')})" definition must be of a single type`, - NO_STATE_ARGUMENT_GIVEN: (name: string, type: string) => - `pseudo-state "${name}" expected argument of type "${type}" but got none`, - NO_STATE_TYPE_GIVEN: (name: string) => - `pseudo-state "${name}" expected a definition of a single type, but received none`, - TOO_MANY_ARGS_IN_VALIDATOR: (name: string, validator: string, args: string[]) => - `pseudo-state "${name}" expected "${validator}" validator to receive a single argument, but it received "${args.join( - ', ' - )}"`, - STATE_STARTS_WITH_HYPHEN: (name: string) => - `state "${name}" declaration cannot begin with a "${stateMiddleDelimiter}" character`, - RESERVED_NATIVE_STATE: (name: string) => `state "${name}" is reserved for native pseudo-class`, +export const stateDiagnostics = { + UNKNOWN_STATE_USAGE: createDiagnosticReporter( + '08001', + 'error', + (name: string) => `unknown pseudo-state "${name}"` + ), + UNKNOWN_STATE_TYPE: createDiagnosticReporter( + '08002', + 'error', + (name: string, type: string) => + `pseudo-state "${name}" defined with unknown type: "${type}"` + ), + TOO_MANY_STATE_TYPES: createDiagnosticReporter( + '08003', + 'error', + (name: string, types: string[]) => + `pseudo-state "${name}(${types.join(', ')})" definition must be of a single type` + ), + NO_STATE_ARGUMENT_GIVEN: createDiagnosticReporter( + '08004', + 'error', + (name: string, type: string) => + `pseudo-state "${name}" expected argument of type "${type}" but got none` + ), + NO_STATE_TYPE_GIVEN: createDiagnosticReporter( + '08005', + 'warning', + (name: string) => + `pseudo-state "${name}" expected a definition of a single type, but received none` + ), + TOO_MANY_ARGS_IN_VALIDATOR: createDiagnosticReporter( + '08006', + 'error', + (name: string, validator: string, args: string[]) => + `pseudo-state "${name}" expected "${validator}" validator to receive a single argument, but it received "${args.join( + ', ' + )}"` + ), + STATE_STARTS_WITH_HYPHEN: createDiagnosticReporter( + '08007', + 'error', + (name: string) => + `state "${name}" declaration cannot begin with a "${stateMiddleDelimiter}" character` + ), + RESERVED_NATIVE_STATE: createDiagnosticReporter( + '08008', + 'warning', + (name: string) => `state "${name}" is reserved for native pseudo-class` + ), + FAILED_STATE_VALIDATION: createDiagnosticReporter( + '08009', + 'error', + (name: string, actualParam: string, errors: string[]) => + [ + `pseudo-state "${name}" with parameter "${actualParam}" failed validation:`, + ...errors, + ].join('\n') + ), + DEFAULT_PARAM_FAILS_VALIDATION: createDiagnosticReporter( + '08010', + 'error', + (stateName: string, defaultValue: string, errors: string[]) => + `pseudo-state "${stateName}" default value "${defaultValue}" failed validation:\n${errors.join( + '\n' + )}` + ), }; // PROCESS @@ -58,11 +101,13 @@ export function processPseudoStates( const [stateDefinition, ...stateDefault] = workingState; if (stateDefinition.value.startsWith('-')) { - diagnostics.error(decl, stateErrors.STATE_STARTS_WITH_HYPHEN(stateDefinition.value), { + diagnostics.report(stateDiagnostics.STATE_STARTS_WITH_HYPHEN(stateDefinition.value), { + node: decl, word: stateDefinition.value, }); } else if (reservedFunctionalPseudoClasses.includes(stateDefinition.value)) { - diagnostics.warn(decl, stateErrors.RESERVED_NATIVE_STATE(stateDefinition.value), { + diagnostics.report(stateDiagnostics.RESERVED_NATIVE_STATE(stateDefinition.value), { + node: decl, word: stateDefinition.value, }); return; @@ -90,7 +135,8 @@ function resolveStateType( if (stateDefinition.type === 'function' && stateDefinition.nodes.length === 0) { resolveBooleanState(mappedStates, stateDefinition); - diagnostics.warn(decl, stateErrors.NO_STATE_TYPE_GIVEN(stateDefinition.value), { + diagnostics.report(stateDiagnostics.NO_STATE_TYPE_GIVEN(stateDefinition.value), { + node: decl, word: decl.value, }); @@ -98,10 +144,15 @@ function resolveStateType( } if (stateDefinition.nodes.length > 1) { - diagnostics.warn( - decl, - stateErrors.TOO_MANY_STATE_TYPES(stateDefinition.value, listOptions(stateDefinition)), - { word: decl.value } + diagnostics.report( + stateDiagnostics.TOO_MANY_STATE_TYPES( + stateDefinition.value, + listOptions(stateDefinition) + ), + { + node: decl, + word: decl.value, + } ); } @@ -127,10 +178,12 @@ function resolveStateType( } else if (stateType.type in systemValidators) { mappedStates[stateDefinition.value] = stateType; } else { - diagnostics.warn( - decl, - stateErrors.UNKNOWN_STATE_TYPE(stateDefinition.value, paramType.value), - { word: paramType.value } + diagnostics.report( + stateDiagnostics.UNKNOWN_STATE_TYPE(stateDefinition.value, paramType.value), + { + node: decl, + word: paramType.value, + } ); } } @@ -149,10 +202,12 @@ function resolveArguments( if (validator.type === 'function') { const args = listOptions(validator); if (args.length > 1) { - diagnostics.warn( - decl, - stateErrors.TOO_MANY_ARGS_IN_VALIDATOR(name, validator.value, args), - { word: decl.value } + diagnostics.report( + stateDiagnostics.TOO_MANY_ARGS_IN_VALIDATOR(name, validator.value, args), + { + node: decl, + word: decl.value, + } ); } else { stateType.arguments.push({ @@ -181,65 +236,6 @@ function resolveBooleanState(mappedStates: MappedStates, stateDefinition: Parsed // TRANSFORM -/* @deprecated */ -export const validateStateDefinition = wrapFunctionForDeprecation( - function ( - decl: postcss.Declaration, - meta: StylableMeta, - resolver: StylableResolver, - diagnostics: Diagnostics - ) { - if (decl.parent && decl.parent.type !== 'root') { - const container = decl.parent; - if (container.type !== 'atrule') { - const parentRule = container as postcss.Rule; - const selectorAst = parseSelectorWithCache(parentRule.selector); - if (selectorAst.length && selectorAst.length === 1) { - const singleSelectorAst = selectorAst[0]; - const selectorChunk = singleSelectorAst.nodes; - - if (selectorChunk.length === 1 && selectorChunk[0].type === 'class') { - const className = selectorChunk[0].value; - const classMeta = CSSClass.get(meta, className)!; - const states = classMeta[`-st-states`]; - - if (classMeta && classMeta._kind === 'class' && states) { - for (const stateName in states) { - // TODO: Sort out types - const state = states[stateName]; - if (state && typeof state === 'object') { - const res = validateStateArgument( - state, - meta, - state.defaultValue || '', - resolver, - diagnostics, - parentRule, - true, - !!state.defaultValue - ); - - if (res.errors) { - res.errors.unshift( - `pseudo-state "${stateName}" default value "${state.defaultValue}" failed validation:` - ); - diagnostics.warn(decl, res.errors.join('\n'), { - word: decl.value, - }); - } - } - } - } else { - // TODO: error state on non-class - } - } - } - } - } - }, - { name: `validateStateDefinition` } -); - export function validateStateArgument( stateAst: StateParsedValue, meta: StylableMeta, @@ -319,14 +315,15 @@ function resolveStateValue( ); if (rule && !inputValue && !stateDef.defaultValue) { - diagnostics.warn(rule, stateErrors.NO_STATE_ARGUMENT_GIVEN(name, stateDef.type), { + diagnostics.report(stateDiagnostics.NO_STATE_ARGUMENT_GIVEN(name, stateDef.type), { + node: rule, word: name, }); } const validator = systemValidators[stateDef.type]; - let stateParamOutput; + let stateParamOutput: StateResult | undefined; try { stateParamOutput = validator.validate( actualParam, @@ -345,11 +342,17 @@ function resolveStateValue( } if (rule && stateParamOutput.errors) { - stateParamOutput.errors.unshift( - `pseudo-state "${name}" with parameter "${actualParam}" failed validation:` + diagnostics.report( + stateDiagnostics.FAILED_STATE_VALIDATION( + name, + actualParam, + stateParamOutput.errors + ), + { + node: rule, + word: actualParam, + } ); - - diagnostics.warn(rule, stateParamOutput.errors.join('\n'), { word: actualParam }); } } diff --git a/packages/core/src/replace-rule-selector.ts b/packages/core/src/replace-rule-selector.ts deleted file mode 100644 index 2a8e7d038..000000000 --- a/packages/core/src/replace-rule-selector.ts +++ /dev/null @@ -1,77 +0,0 @@ -// sourced from https://github.com/postcss/postcss-selector-matches - -import { list, Rule } from 'postcss'; -import balancedMatch from 'balanced-match'; - -const pseudoClass = ':matches'; -const selectorElementRE = /^[a-zA-Z]/; - -function isElementSelector(selector: string) { - const matches = selectorElementRE.exec(selector); - return matches; -} - -function normalizeSelector(selector: string, preWhitespace: string, pre: string) { - if (isElementSelector(selector) && !isElementSelector(pre)) { - return `${preWhitespace}${selector}${pre}`; - } - - return `${preWhitespace}${pre}${selector}`; -} - -function explodeSelector(selector: string, options: { lineBreak: boolean }) { - if (selector && selector.indexOf(pseudoClass) > -1) { - let newSelectors: string[] = []; - const preWhitespaceMatches = selector.match(/^\s+/); - const preWhitespace = preWhitespaceMatches ? preWhitespaceMatches[0] : ''; - const selectorPart = list.comma(selector); - selectorPart.forEach((part) => { - const position = part.indexOf(pseudoClass); - const pre = part.slice(0, position); - const body = part.slice(position); - const matches = balancedMatch('(', ')', body); - - const bodySelectors = - matches && matches.body - ? list - .comma(matches.body) - .reduce( - (acc, s) => [...acc, ...explodeSelector(s, options)], - [] - ) - : [body]; - - const postSelectors = - matches && matches.post ? explodeSelector(matches.post, options) : []; - - let newParts: string[]; - if (postSelectors.length === 0) { - // the test below is a poor way to try we are facing a piece of a - // selector... - if (position === -1 || pre.indexOf(' ') > -1) { - newParts = bodySelectors.map((s) => preWhitespace + pre + s); - } else { - newParts = bodySelectors.map((s) => normalizeSelector(s, preWhitespace, pre)); - } - } else { - newParts = []; - postSelectors.forEach((postS) => { - bodySelectors.forEach((s) => { - newParts.push(preWhitespace + pre + s + postS); - }); - }); - } - newSelectors = [...newSelectors, ...newParts]; - }); - - return newSelectors; - } - return [selector]; -} - -export function replaceRuleSelector(rule: Rule, options: { lineBreak: boolean }) { - const indentation = rule.raws && rule.raws.before ? rule.raws.before.split('\n').pop() : ''; - return explodeSelector(rule.selector, options).join( - ',' + (options.lineBreak ? '\n' + indentation : ' ') - ); -} diff --git a/packages/core/src/report-diagnostic.ts b/packages/core/src/report-diagnostic.ts index 4c77fd359..2a2444e33 100644 --- a/packages/core/src/report-diagnostic.ts +++ b/packages/core/src/report-diagnostic.ts @@ -1,4 +1,4 @@ -import type { Diagnostic, DiagnosticType } from './diagnostics'; +import type { Diagnostic } from './diagnostics'; import type { StylableMeta } from './stylable-meta'; export interface EmitDiagnosticsContext { @@ -13,21 +13,22 @@ export type DiagnosticsMode = 'auto' | 'strict' | 'loose'; export function reportDiagnostic( ctx: EmitDiagnosticsContext, diagnosticsMode: DiagnosticsMode, - { message, type }: { message: string; type: DiagnosticType }, + { code, message, severity }: Diagnostic, from?: string ) { - const error = new Error(from ? `[${from}]:\n\n${message}` : message); + const messageToPrint = `[${severity}: ${code}]: ${message}`; + const error = new Error(from ? `[${from}]:\n\n${messageToPrint}` : messageToPrint); - if (type === 'info') { + if (severity === 'info') { ctx.emitWarning(error); return; } if (diagnosticsMode === 'auto') { - if (type === 'warning') { + if (severity === 'warning') { ctx.emitWarning(error); - } else if (type === 'error') { + } else if (severity === 'error') { ctx.emitError(error); } } else if (diagnosticsMode === 'strict') { diff --git a/packages/core/src/state-validators.ts b/packages/core/src/state-validators.ts index 8b02237fc..dfdd468fd 100644 --- a/packages/core/src/state-validators.ts +++ b/packages/core/src/state-validators.ts @@ -35,10 +35,6 @@ const validationErrors = { `expected "${actualParam}" to be one of the options: "${options.join(', ')}"`, NO_OPTIONS_DEFINED: () => `expected enum to be defined with one option or more`, }, - tag: { - NO_SPACES_ALLOWED: (actualParam: string) => - `expected "${actualParam}" to be a single value with no spaces`, - }, }; export type SubValidator = (value: string, ...rest: string[]) => StateResult; @@ -250,21 +246,4 @@ export const systemValidators: Record = { return { res, errors: errors.length ? errors : null }; }, }, - tag: { - validate( - value: any, - _options: StateArguments, - _resolveParam: (s: string) => string, - _validateDefinition, - validateValue - ) { - const errors: string[] = []; - - if (validateValue && ~value.indexOf(' ')) { - errors.push(validationErrors.tag.NO_SPACES_ALLOWED(value)); - } - - return { res: value, errors: errors.length ? errors : null }; - }, - }, }; diff --git a/packages/core/src/stylable-assets.ts b/packages/core/src/stylable-assets.ts index 98e2ff1dc..fd4055908 100644 --- a/packages/core/src/stylable-assets.ts +++ b/packages/core/src/stylable-assets.ts @@ -2,40 +2,11 @@ import path from 'path'; import type * as postcss from 'postcss'; import { processDeclarationFunctions } from './process-declaration-functions'; -export interface UrlNode { - type: 'url'; - url: string; - stringType?: string; - name?: string; - before?: string; - after?: string; - innerSpacingBefore?: string; - innerSpacingAfter?: string; -} - -export type OnUrlCallback = (node: UrlNode) => void; - -export function collectAssets(ast: postcss.Root) { - const assetDependencies: string[] = []; - ast.walkDecls((decl) => { - processDeclarationFunctions( - decl, - (node) => { - if (node.type === 'url') { - assetDependencies.push(node.url); - } - }, - false - ); - }); - return assetDependencies; -} - -export function isExternal(url: string) { +function isExternal(url: string) { return url === '' || url.startsWith('data:') || isUrl(url); } -export function isUrl(maybeUrl: string) { +function isUrl(maybeUrl: string) { maybeUrl = maybeUrl.trim(); if (maybeUrl.includes(' ')) { return false; diff --git a/packages/core/src/stylable-meta.ts b/packages/core/src/stylable-meta.ts index 036d2070e..ca6aec05e 100644 --- a/packages/core/src/stylable-meta.ts +++ b/packages/core/src/stylable-meta.ts @@ -1,25 +1,16 @@ import type * as postcss from 'postcss'; -import type { - CSSVarSymbol, - ClassSymbol, - ElementSymbol, - Imported, - KeyframesSymbol, - RefedMixin, - StylableSymbol, - VarSymbol, - FeatureContext, -} from './features'; +import type { FeatureContext } from './features'; import type { Diagnostics } from './diagnostics'; import type { SelectorList } from '@tokey/css-selector-parser'; import type { PlugableRecord } from './helpers/plugable-record'; import { getSourcePath } from './stylable-utils'; -import { setFieldForDeprecation } from './helpers/deprecation'; import { STSymbol, STImport, STGlobal, + STScope, STVar, + STCustomSelector, STMixin, CSSClass, CSSType, @@ -28,13 +19,13 @@ import { CSSLayer, } from './features'; -export const RESERVED_ROOT_NAME = 'root'; - const features = [ STSymbol, STImport, STGlobal, + STScope, STVar, + STCustomSelector, STMixin, CSSClass, CSSType, @@ -45,46 +36,25 @@ const features = [ export class StylableMeta { public data: PlugableRecord = {}; - public rawAst: postcss.Root = this.ast.clone(); - public root: 'root' = RESERVED_ROOT_NAME; - public source: string = getSourcePath(this.ast, this.diagnostics); + public root = 'root'; + public source: string = getSourcePath(this.sourceAst, this.diagnostics); public namespace = ''; - /** @deprecated use meta.getImportStatements() */ - public imports: Imported[] = []; - /** @deprecated use meta.getAllStVars() or meta.getStVar(name) */ - public vars: VarSymbol[] = []; - /** @deprecated */ - public cssVars: Record = {}; - /** @deprecated */ - public keyframes: postcss.AtRule[] = []; - /** @deprecated use meta.getAllClasses() or meta.getClass(name) */ - public classes: Record = {}; - /** @deprecated use meta.getAllTypeElements() or meta.getTypeElement(name) */ - public elements: Record = {}; - /** @deprecated use meta.getAllSymbols() or meta.getSymbol(name) */ - public mappedSymbols: Record = {}; - /** @deprecated */ - public mappedKeyframes: Record = {}; - /** @deprecated */ - public customSelectors: Record = {}; public urls: string[] = []; public transformDiagnostics: Diagnostics | null = null; public transformedScopes: Record | null = null; /** @deprecated */ public scopes: postcss.AtRule[] = []; - /** @deprecated */ - public mixins: RefedMixin[] = []; // Generated during transform - public outputAst?: postcss.Root; + public targetAst?: postcss.Root; public globals: Record = {}; - constructor(public ast: postcss.Root, public diagnostics: Diagnostics) { + constructor(public sourceAst: postcss.Root, public diagnostics: Diagnostics) { // initiate features const context: FeatureContext = { meta: this, diagnostics }; for (const { hooks } of features) { hooks.metaInit(context); } // set default root - const rootSymbol = CSSClass.addClass(context, RESERVED_ROOT_NAME); + const rootSymbol = CSSClass.addClass(context, 'root'); rootSymbol[`-st-root`] = true; } getSymbol(name: string) { @@ -115,44 +85,3 @@ export class StylableMeta { return STSymbol.getAllByType(this, `var`); } } -setFieldForDeprecation(StylableMeta.prototype, `elements`, { - objectType: `stylableMeta`, - valueOnThis: true, - pleaseUse: `meta.getAllTypeElements() or meta.getTypeElement(name)`, -}); -setFieldForDeprecation(StylableMeta.prototype, `classes`, { - objectType: `stylableMeta`, - valueOnThis: true, - pleaseUse: `meta.getAllClasses() or meta.getClass(name)`, -}); -setFieldForDeprecation(StylableMeta.prototype, `mappedSymbols`, { - objectType: `stylableMeta`, - valueOnThis: true, - pleaseUse: `meta.getAllSymbols() or meta.getSymbol(name)`, -}); -setFieldForDeprecation(StylableMeta.prototype, `imports`, { - objectType: `stylableMeta`, - valueOnThis: true, - pleaseUse: `meta.getImportStatements()`, -}); -setFieldForDeprecation(StylableMeta.prototype, `keyframes`, { - objectType: `stylableMeta`, - valueOnThis: true, -}); -setFieldForDeprecation(StylableMeta.prototype, `mappedKeyframes`, { - objectType: `stylableMeta`, - valueOnThis: true, -}); -setFieldForDeprecation(StylableMeta.prototype, `cssVars`, { - objectType: `stylableMeta`, - valueOnThis: true, -}); -setFieldForDeprecation(StylableMeta.prototype, `vars`, { - objectType: `stylableMeta`, - valueOnThis: true, - pleaseUse: `meta.getAllStVars() or meta.getStVar(name)`, -}); -setFieldForDeprecation(StylableMeta.prototype, `mixins`, { - objectType: `stylableMeta`, - valueOnThis: true, -}); diff --git a/packages/core/src/stylable-processor.ts b/packages/core/src/stylable-processor.ts index e66ffcc4d..06a87fcea 100644 --- a/packages/core/src/stylable-processor.ts +++ b/packages/core/src/stylable-processor.ts @@ -1,7 +1,6 @@ import path from 'path'; -import * as postcss from 'postcss'; -import { Diagnostics } from './diagnostics'; -import { parseSelector as deprecatedParseSelector } from './deprecated/deprecated-selector-utils'; +import type * as postcss from 'postcss'; +import { createDiagnosticReporter, Diagnostics } from './diagnostics'; import { murmurhash3_32_gc } from './murmurhash'; import { knownPseudoClassesWithNestedSelectors } from './native-reserved-lists'; import { StylableMeta } from './stylable-meta'; @@ -11,7 +10,7 @@ import { ElementSymbol, StylableDirectives, STVar, - STMixin, + STCustomSelector, } from './features'; import { generalDiagnostics } from './features/diagnostics'; import { @@ -25,7 +24,7 @@ import { CSSKeyframes, CSSLayer, } from './features'; -import { CUSTOM_SELECTOR_RE, expandCustomSelectors, getAlias } from './stylable-utils'; +import { getAlias } from './stylable-utils'; import { processDeclarationFunctions } from './process-declaration-functions'; import { walkSelector, @@ -35,50 +34,72 @@ import { stringifySelector, } from './helpers/selector'; import { isChildOfAtRule } from './helpers/rule'; -import type { SRule } from './deprecated/postcss-ast-extension'; -import { stValuesMap } from './deprecated/value-mapping'; import { SBTypesParsers } from './stylable-value-parsers'; import { stripQuotation, filename2varname } from './helpers/string'; -import { warnOnce } from './helpers/deprecation'; const parseStates = SBTypesParsers[`-st-states`]; const parseGlobal = SBTypesParsers[`-st-global`]; const parseExtends = SBTypesParsers[`-st-extends`]; -export const processorWarnings = { - STATE_DEFINITION_IN_ELEMENT() { - return 'cannot define pseudo states inside a type selector'; - }, - STATE_DEFINITION_IN_COMPLEX() { - return 'cannot define pseudo states inside complex selectors'; - }, - CANNOT_RESOLVE_EXTEND(name: string) { - return `cannot resolve '-st-extends' type for '${name}'`; - }, - CANNOT_EXTEND_IN_COMPLEX() { - return `cannot define "-st-extends" inside a complex selector`; - }, - OVERRIDE_TYPED_RULE(key: string, name: string) { - return `override "${key}" on typed rule "${name}"`; - }, - INVALID_NAMESPACE_DEF() { - return 'invalid @namespace'; - }, - EMPTY_NAMESPACE_DEF() { - return '@namespace must contain at least one character or digit'; - }, - - INVALID_NAMESPACE_REFERENCE() { - return 'st-namespace-reference dose not have any value'; - }, - INVALID_NESTING(child: string, parent: string) { - return `nesting of rules within rules is not supported, found: "${child}" inside "${parent}"`; - }, +const stValuesMap = { + '-st-from': true, + '-st-named': true, + '-st-default': true, + '-st-root': true, + '-st-states': true, + '-st-extends': true, + '-st-mixin': true, + '-st-partial-mixin': true, + '-st-global': true, +} as const; + +export const processorDiagnostics = { + STATE_DEFINITION_IN_ELEMENT: createDiagnosticReporter( + '11002', + 'error', + () => 'cannot define pseudo states inside a type selector' + ), + STATE_DEFINITION_IN_COMPLEX: createDiagnosticReporter( + '11003', + 'error', + () => 'cannot define pseudo states inside complex selectors' + ), + CANNOT_RESOLVE_EXTEND: createDiagnosticReporter( + '11004', + 'error', + (name: string) => `cannot resolve '-st-extends' type for '${name}'` + ), + CANNOT_EXTEND_IN_COMPLEX: createDiagnosticReporter( + '11005', + 'error', + () => `cannot define "-st-extends" inside a complex selector` + ), + OVERRIDE_TYPED_RULE: createDiagnosticReporter( + '11006', + 'warning', + (key: string, name: string) => `override "${key}" on typed rule "${name}"` + ), + INVALID_NAMESPACE_DEF: createDiagnosticReporter('11007', 'error', () => 'invalid @namespace'), + EMPTY_NAMESPACE_DEF: createDiagnosticReporter( + '11008', + 'error', + () => '@namespace must contain at least one character or digit' + ), + INVALID_NAMESPACE_REFERENCE: createDiagnosticReporter( + '11010', + 'error', + () => 'st-namespace-reference dose not have any value' + ), + INVALID_NESTING: createDiagnosticReporter( + '11011', + 'error', + (child: string, parent: string) => + `nesting of rules within rules is not supported, found: "${child}" inside "${parent}"` + ), }; export class StylableProcessor implements FeatureContext { public meta!: StylableMeta; - private customSelectorData: Record = {}; constructor( public diagnostics = new Diagnostics(), private resolveNamespace = processNamespace @@ -93,19 +114,19 @@ export class StylableProcessor implements FeatureContext { root.walkRules((rule) => { if (!isChildOfAtRule(rule, 'keyframes')) { - this.handleRule(rule as SRule, { + this.handleRule(rule, { isScoped: isChildOfAtRule(rule, `st-scope`), reportUnscoped: true, }); } const parent = rule.parent; if (parent?.type === 'rule') { - this.diagnostics.error( - rule, - processorWarnings.INVALID_NESTING( + this.diagnostics.report( + processorDiagnostics.INVALID_NESTING( rule.selector, (parent as postcss.Rule).selector - ) + ), + { node: rule } ); } }); @@ -119,18 +140,17 @@ export class StylableProcessor implements FeatureContext { return; } // ToDo: refactor to be hooked by features - if (stValuesMap[decl.prop] && parent.type === 'rule') { - this.handleDirectives(parent as SRule, decl); + if (decl.prop in stValuesMap && parent.type === 'rule') { + this.handleDirectives(parent, decl); } CSSCustomProperty.hooks.analyzeDeclaration({ context: this, decl }); this.collectUrls(decl); }); + STCustomSelector.hooks.analyzeDone(this); STSymbol.reportRedeclare(this); - prepareAST(this.meta, root); - return this.meta; } @@ -138,7 +158,7 @@ export class StylableProcessor implements FeatureContext { let namespace = ''; const analyzeRule = (rule: postcss.Rule, { isScoped }: { isScoped: boolean }) => { - return this.handleRule(rule as SRule, { + return this.handleRule(rule, { isScoped, reportUnscoped: false, }); @@ -160,10 +180,14 @@ export class StylableProcessor implements FeatureContext { if (match[1].trim()) { namespace = match[1]; } else { - this.diagnostics.error(atRule, processorWarnings.EMPTY_NAMESPACE_DEF()); + this.diagnostics.report(processorDiagnostics.EMPTY_NAMESPACE_DEF(), { + node: atRule, + }); } } else { - this.diagnostics.error(atRule, processorWarnings.INVALID_NAMESPACE_DEF()); + this.diagnostics.report(processorDiagnostics.INVALID_NAMESPACE_DEF(), { + node: atRule, + }); } break; } @@ -189,21 +213,11 @@ export class StylableProcessor implements FeatureContext { }); break; case 'custom-selector': { - const params = atRule.params.split(/\s/); - const customName = params.shift(); - if (customName && customName.match(CUSTOM_SELECTOR_RE)) { - const selector = atRule.params.replace(customName, '').trim(); - const isScoped = analyzeRule( - postcss.rule({ selector, source: atRule.source }), - { isScoped: false } - ); - this.customSelectorData[customName] = { - isScoped, - }; - this.meta.customSelectors[customName] = selector; - } else { - // TODO: add warn there are two types one is not valid name and the other is empty name. - } + STCustomSelector.hooks.analyzeAtRule({ + context: this, + atRule, + analyzeRule, + }); break; } case 'st-scope': @@ -233,14 +247,16 @@ export class StylableProcessor implements FeatureContext { private handleNamespaceReference(namespace: string): string { let pathToSource: string | undefined; - let length = this.meta.ast.nodes.length; + let length = this.meta.sourceAst.nodes.length; while (length--) { - const node = this.meta.ast.nodes[length]; + const node = this.meta.sourceAst.nodes[length]; if (node.type === 'comment' && node.text.includes('st-namespace-reference')) { const i = node.text.indexOf('='); if (i === -1) { - this.diagnostics.error(node, processorWarnings.INVALID_NAMESPACE_REFERENCE()); + this.diagnostics.report(processorDiagnostics.INVALID_NAMESPACE_REFERENCE(), { + node, + }); } else { pathToSource = stripQuotation(node.text.slice(i + 1)); } @@ -258,15 +274,13 @@ export class StylableProcessor implements FeatureContext { } protected handleRule( - rule: SRule, + rule: postcss.Rule, { isScoped, reportUnscoped }: { isScoped: boolean; reportUnscoped: boolean } ) { - rule.selectorAst = deprecatedParseSelector(rule.selector); - const selectorAst = parseSelectorWithCache(rule.selector); let locallyScoped = isScoped; - let simpleSelector: boolean; + walkSelector(selectorAst, (node, ...nodeContext) => { const [index, nodes, parents] = nodeContext; const type = node.type; @@ -274,9 +288,6 @@ export class StylableProcessor implements FeatureContext { // reset scope check between top level selectors locallyScoped = isScoped; } - if (type !== `selector` && type !== `class` && type !== `type`) { - simpleSelector = false; - } if (node.type === 'pseudo_class') { if (node.value === 'import') { @@ -301,9 +312,10 @@ export class StylableProcessor implements FeatureContext { walkContext: nodeContext, }); } else if (node.value.startsWith('--')) { + // ToDo: move to css-class feature locallyScoped = locallyScoped || - this.customSelectorData[`:${node.value}`]?.isScoped || + STCustomSelector.isScoped(this.meta, node.value.slice(2)) || false; } else if (!knownPseudoClassesWithNestedSelectors.includes(node.value)) { return walkSelector.skipNested; @@ -345,33 +357,33 @@ export class StylableProcessor implements FeatureContext { }); } else if (node.type === `id`) { if (node.nodes) { - this.diagnostics.error( - rule, + this.diagnostics.report( generalDiagnostics.INVALID_FUNCTIONAL_SELECTOR(`#` + node.value, `id`), { + node: rule, word: stringifySelector(node), } ); } } else if (node.type === `attribute`) { if (node.nodes) { - this.diagnostics.error( - rule, + this.diagnostics.report( generalDiagnostics.INVALID_FUNCTIONAL_SELECTOR( `[${node.value}]`, `attribute` ), { + node: rule, word: stringifySelector(node), } ); } } else if (node.type === `nesting`) { if (node.nodes) { - this.diagnostics.error( - rule, + this.diagnostics.report( generalDiagnostics.INVALID_FUNCTIONAL_SELECTOR(node.value, `nesting`), { + node: rule, word: stringifySelector(node), } ); @@ -380,17 +392,10 @@ export class StylableProcessor implements FeatureContext { return; }); - if (simpleSelector! !== false) { - rule.isSimpleSelector = true; - rule.selectorType = rule.selector.match(/^\./) ? 'class' : 'element'; - } else { - rule.selectorType = 'complex'; - } - return locallyScoped; } - protected handleDirectives(rule: SRule, decl: postcss.Declaration) { + protected handleDirectives(rule: postcss.Rule, decl: postcss.Declaration) { const isSimplePerSelector = isSimpleSelector(rule.selector); const type = isSimplePerSelector.reduce((accType, { type }) => { return !accType ? type : accType !== type ? `complex` : type; @@ -406,9 +411,13 @@ export class StylableProcessor implements FeatureContext { ); } else { if (type === 'type') { - this.diagnostics.warn(decl, processorWarnings.STATE_DEFINITION_IN_ELEMENT()); + this.diagnostics.report(processorDiagnostics.STATE_DEFINITION_IN_ELEMENT(), { + node: decl, + }); } else { - this.diagnostics.warn(decl, processorWarnings.STATE_DEFINITION_IN_COMPLEX()); + this.diagnostics.report(processorDiagnostics.STATE_DEFINITION_IN_COMPLEX(), { + node: decl, + }); } } } else if (decl.prop === `-st-extends`) { @@ -431,17 +440,19 @@ export class StylableProcessor implements FeatureContext { getAlias(extendsRefSymbol) || extendsRefSymbol ); } else { - this.diagnostics.warn( - decl, - processorWarnings.CANNOT_RESOLVE_EXTEND(decl.value), - { word: decl.value } + this.diagnostics.report( + processorDiagnostics.CANNOT_RESOLVE_EXTEND(decl.value), + { + node: decl, + word: decl.value, + } ); } } else { - this.diagnostics.warn(decl, processorWarnings.CANNOT_EXTEND_IN_COMPLEX()); + this.diagnostics.report(processorDiagnostics.CANNOT_EXTEND_IN_COMPLEX(), { + node: decl, + }); } - } else if (decl.prop === STMixin.MixinType.ALL || decl.prop === STMixin.MixinType.PARTIAL) { - STMixin.hooks.analyzeDeclaration({ context: this, decl }); } else if (decl.prop === `-st-global`) { if (isSimple && type !== 'type') { this.setClassGlobalMapping(decl, rule); @@ -471,7 +482,8 @@ export class StylableProcessor implements FeatureContext { const name = selector.replace('.', ''); const typedRule = STSymbol.get(this.meta, name) as ClassSymbol | ElementSymbol; if (typedRule && typedRule[key]) { - this.diagnostics.warn(node, processorWarnings.OVERRIDE_TYPED_RULE(key, name), { + this.diagnostics.report(processorDiagnostics.OVERRIDE_TYPED_RULE(key, name), { + node, word: name, }); } @@ -481,57 +493,6 @@ export class StylableProcessor implements FeatureContext { } } -/* @deprecated */ -export function validateScopingSelector( - atRule: postcss.AtRule, - { selector: scopingSelector }: SRule, - diagnostics: Diagnostics -) { - if (!scopingSelector) { - diagnostics.warn(atRule, STScope.diagnostics.MISSING_SCOPING_PARAM()); - } -} - -export function createEmptyMeta(root: postcss.Root, diagnostics: Diagnostics): StylableMeta { - warnOnce( - 'createEmptyMeta is deprecated and will be removed in the next version. Use "new StylableMeta()"' - ); - return new StylableMeta(root, diagnostics); -} - export function processNamespace(namespace: string, origin: string, _source?: string) { return namespace + murmurhash3_32_gc(origin); // .toString(36); } - -export function process( - root: postcss.Root, - diagnostics = new Diagnostics(), - resolveNamespace?: typeof processNamespace -) { - return new StylableProcessor(diagnostics, resolveNamespace).process(root); -} - -export function prepareAST(meta: StylableMeta, ast: postcss.Root) { - const toRemove: Array void)> = []; - ast.walk((node) => { - const input = { node, toRemove }; - // namespace - if (node.type === 'atrule' && node.name === `namespace`) { - toRemove.push(node); - } - // custom selectors - if (node.type === 'rule') { - expandCustomSelectors(node, meta.customSelectors, meta.diagnostics); - } else if (node.type === 'atrule' && node.name === 'custom-selector') { - toRemove.push(node); - } - // extracted features - STImport.hooks.prepareAST(input); - STScope.hooks.prepareAST(input); - STVar.hooks.prepareAST(input); - CSSCustomProperty.hooks.prepareAST(input); - }); - for (const removeOrNode of toRemove) { - typeof removeOrNode === 'function' ? removeOrNode() : removeOrNode.remove(); - } -} diff --git a/packages/core/src/stylable-resolver.ts b/packages/core/src/stylable-resolver.ts index 08f39d135..9d76c5514 100644 --- a/packages/core/src/stylable-resolver.ts +++ b/packages/core/src/stylable-resolver.ts @@ -9,6 +9,7 @@ import { StylableSymbol, CSSClass, STSymbol, + STCustomSelector, VarSymbol, CSSVarSymbol, KeyframesSymbol, @@ -85,7 +86,7 @@ export type ReportError = ( isElement: boolean ) => void; -export function isInPath( +function isInPath( extendPath: Array>, { symbol: { name: name1 }, meta: { source: source1 } }: CSSResolve ) { @@ -397,7 +398,9 @@ export class StylableResolver { : meta.getClass(nameOrSymbol) : nameOrSymbol; - const customSelector = isElement ? null : meta.customSelectors[':--' + name]; + const customSelector = isElement + ? null + : STCustomSelector.getCustomSelectorExpended(meta, name); if (!symbol && !customSelector) { return []; @@ -476,28 +479,30 @@ function validateClassResolveExtends( deepResolved: CSSResolve | JSResolve | null ): ReportError | undefined { return (res, extend) => { - const decl = findRule(meta.ast, '.' + name); + const decl = findRule(meta.sourceAst, '.' + name); if (decl) { // ToDo: move to STExtends if (res && res._kind === 'js') { - diagnostics.error(decl, CSSClass.diagnostics.CANNOT_EXTEND_JS(), { + diagnostics.report(CSSClass.diagnostics.CANNOT_EXTEND_JS(), { + node: decl, word: decl.value, }); } else if (res && !res.symbol) { - diagnostics.error( - decl, - CSSClass.diagnostics.CANNOT_EXTEND_UNKNOWN_SYMBOL(extend.name), - { word: decl.value } - ); + diagnostics.report(CSSClass.diagnostics.CANNOT_EXTEND_UNKNOWN_SYMBOL(extend.name), { + node: decl, + word: decl.value, + }); } else { - diagnostics.error(decl, CSSClass.diagnostics.IMPORT_ISNT_EXTENDABLE(), { + diagnostics.report(CSSClass.diagnostics.IMPORT_ISNT_EXTENDABLE(), { + node: decl, word: decl.value, }); } } else { if (deepResolved?.symbol.alias) { - meta.ast.walkRules(new RegExp('\\.' + name), (rule) => { - diagnostics.error(rule, CSSClass.diagnostics.UNKNOWN_IMPORT_ALIAS(name), { + meta.sourceAst.walkRules(new RegExp('\\.' + name), (rule) => { + diagnostics.report(CSSClass.diagnostics.UNKNOWN_IMPORT_ALIAS(name), { + node: rule, word: name, }); return false; diff --git a/packages/core/src/stylable-transformer.ts b/packages/core/src/stylable-transformer.ts index 9660b48ac..45a311246 100644 --- a/packages/core/src/stylable-transformer.ts +++ b/packages/core/src/stylable-transformer.ts @@ -3,10 +3,10 @@ import cloneDeep from 'lodash.clonedeep'; import { basename } from 'path'; import * as postcss from 'postcss'; import type { FileProcessor } from './cached-process-file'; -import type { Diagnostics } from './diagnostics'; +import { createDiagnosticReporter, Diagnostics } from './diagnostics'; import { StylableEvaluator } from './functions'; import { nativePseudoClasses, nativePseudoElements } from './native-reserved-lists'; -import { setStateToNode, stateErrors } from './pseudo-states'; +import { setStateToNode, stateDiagnostics } from './pseudo-states'; import { parseSelectorWithCache, stringifySelector } from './helpers/selector'; import { SelectorNode, @@ -16,23 +16,24 @@ import { CompoundSelector, splitCompoundSelectors, } from '@tokey/css-selector-parser'; -import { createWarningRule, isChildOfAtRule, getRuleScopeSelector } from './helpers/rule'; -import { namespace } from './helpers/namespace'; +import { createWarningRule, isChildOfAtRule } from './helpers/rule'; import { getOriginDefinition } from './helpers/resolve'; -import { ClassSymbol, ElementSymbol, STMixin } from './features'; +import type { ClassSymbol, ElementSymbol, FeatureTransformContext } from './features'; import type { StylableMeta } from './stylable-meta'; import { STSymbol, STImport, STGlobal, + STScope, + STCustomSelector, STVar, + STMixin, CSSClass, CSSType, CSSKeyframes, CSSLayer, CSSCustomProperty, } from './features'; -import type { SDecl } from './deprecated/postcss-ast-extension'; import { CSSResolve, StylableResolverCache, @@ -42,6 +43,7 @@ import { import { validateCustomPropertyName } from './helpers/css-custom-property'; import { namespaceEscape } from './helpers/escape'; import type { ModuleResolver } from './types'; +import { getRuleScopeSelector } from './deprecated/postcss-ast-extension'; const { hasOwnProperty } = Object.prototype; @@ -51,11 +53,6 @@ export interface ResolvedElement { resolved: Array>; } -export interface KeyFrameWithNode { - value: string; - node: postcss.Node; -} - export type RuntimeStVar = string | { [key: string]: RuntimeStVar } | RuntimeStVar[]; export interface StylableExports { @@ -95,35 +92,36 @@ export interface TransformerOptions { moduleResolver: ModuleResolver; requireModule: (modulePath: string) => any; diagnostics: Diagnostics; - delimiter?: string; keepValues?: boolean; replaceValueHook?: replaceValueHook; postProcessor?: postProcessor; mode?: EnvMode; resolverCache?: StylableResolverCache; + stVarOverride?: Record; } -export const transformerWarnings = { - UNKNOWN_PSEUDO_ELEMENT(name: string) { - return `unknown pseudo element "${name}"`; - }, +export const transformerDiagnostics = { + UNKNOWN_PSEUDO_ELEMENT: createDiagnosticReporter( + '12001', + 'error', + (name: string) => `unknown pseudo element "${name}"` + ), }; export class StylableTransformer { public fileProcessor: FileProcessor; public diagnostics: Diagnostics; public resolver: StylableResolver; - public delimiter: string; public keepValues: boolean; public replaceValueHook: replaceValueHook | undefined; public postProcessor: postProcessor | undefined; public mode: EnvMode; + private defaultStVarOverride: Record; private evaluator: StylableEvaluator = new StylableEvaluator(); private getResolvedSymbols: ReturnType; constructor(options: TransformerOptions) { this.diagnostics = options.diagnostics; - this.delimiter = options.delimiter || '__'; this.keepValues = options.keepValues || false; this.fileProcessor = options.fileProcessor; this.replaceValueHook = options.replaceValueHook; @@ -135,6 +133,7 @@ export class StylableTransformer { options.resolverCache || new Map() ); this.mode = options.mode || 'production'; + this.defaultStVarOverride = options.stVarOverride || {}; this.getResolvedSymbols = createSymbolResolverWithCache(this.resolver, this.diagnostics); } public transform(meta: StylableMeta): StylableResults { @@ -146,7 +145,7 @@ export class StylableTransformer { layers: {}, }; meta.transformedScopes = null; - meta.outputAst = meta.ast.clone(); + meta.targetAst = meta.sourceAst.clone(); const context = { meta, diagnostics: this.diagnostics, @@ -157,7 +156,7 @@ export class StylableTransformer { STImport.hooks.transformInit({ context }); STGlobal.hooks.transformInit({ context }); meta.transformedScopes = validateScopes(this, meta); - this.transformAst(meta.outputAst, meta, metaExports); + this.transformAst(meta.targetAst, meta, metaExports); meta.transformDiagnostics = this.diagnostics; const result = { meta, exports: metaExports }; @@ -167,7 +166,7 @@ export class StylableTransformer { ast: postcss.Root, meta: StylableMeta, metaExports?: StylableExports, - stVarOverride?: Record, + stVarOverride: Record = this.defaultStVarOverride, path: string[] = [], mixinTransform = false, topNestClassName = `` @@ -184,6 +183,7 @@ export class StylableTransformer { const transformResolveOptions = { context: transformContext, }; + prepareAST(transformContext, ast); const cssClassResolve = CSSClass.hooks.transformResolve(transformResolveOptions); const stVarResolve = STVar.hooks.transformResolve(transformResolveOptions); const keyframesResolve = CSSKeyframes.hooks.transformResolve(transformResolveOptions); @@ -236,8 +236,6 @@ export class StylableTransformer { }); ast.walkDecls((decl) => { - (decl as SDecl).stylable = { sourceValue: decl.value }; - if (validateCustomPropertyName(decl.prop)) { CSSCustomProperty.hooks.transformDeclaration({ context: transformContext, @@ -269,7 +267,7 @@ export class StylableTransformer { } }); - if (!mixinTransform && meta.outputAst && this.mode === 'development') { + if (!mixinTransform && meta.targetAst && this.mode === 'development') { this.addDevRules(meta); } @@ -311,18 +309,6 @@ export class StylableTransformer { // restore evaluator state this.evaluator.stVarOverride = prevStVarOverride; } - /** @deprecated */ - public getScopedCSSVar( - decl: postcss.Declaration, - meta: StylableMeta, - cssVarsMapping: Record - ) { - let prop = decl.prop; - if (CSSCustomProperty.get(meta, prop)) { - prop = cssVarsMapping[prop]; - } - return prop; - } public resolveSelectorElements(meta: StylableMeta, selector: string): ResolvedElement[][] { return this.scopeSelector(meta, selector).elements; } @@ -335,9 +321,6 @@ export class StylableTransformer { return this.scopeSelector(meta, rule.selector, rule, topNestClassName, unwrapGlobals) .selector; } - public scope(name: string, ns: string, delimiter: string = this.delimiter) { - return namespace(name, ns, delimiter); - } public scopeSelector( originMeta: StylableMeta, selector: string, @@ -405,12 +388,12 @@ export class StylableTransformer { if (selectorList.length === 0) { context.elements.push([]); } - const outputAst = splitCompoundSelectors(selectorList); - context.additionalSelectors.forEach((addSelector) => outputAst.push(addSelector())); - for (let i = 0; i < outputAst.length; i++) { - selectorAst[i] = outputAst[i]; + const targetAst = splitCompoundSelectors(selectorList); + context.additionalSelectors.forEach((addSelector) => targetAst.push(addSelector())); + for (let i = 0; i < targetAst.length; i++) { + selectorAst[i] = targetAst[i]; } - return outputAst; + return targetAst; } private handleCompoundNode(context: Required) { const { currentAnchor, node, originMeta, topNestClassName } = context; @@ -452,7 +435,7 @@ export class StylableTransformer { } const isFirstInSelector = context.selectorAst[context.selectorIndex].nodes[0] === node; - const customSelector = meta.customSelectors[':--' + node.value]; + const customSelector = STCustomSelector.getCustomSelectorExpended(meta, node.value); if (customSelector) { this.handleCustomSelector( customSelector, @@ -504,10 +487,10 @@ export class StylableTransformer { !isVendorPrefixed(node.value) && !this.isDuplicateStScopeDiagnostic(context) ) { - this.diagnostics.warn( - context.rule, - transformerWarnings.UNKNOWN_PSEUDO_ELEMENT(node.value), + this.diagnostics.report( + transformerDiagnostics.UNKNOWN_PSEUDO_ELEMENT(node.value), { + node: context.rule, word: node.value, } ); @@ -566,7 +549,8 @@ export class StylableTransformer { !isVendorPrefixed(node.value) && !this.isDuplicateStScopeDiagnostic(context) ) { - this.diagnostics.warn(context.rule, stateErrors.UNKNOWN_STATE_USAGE(node.value), { + this.diagnostics.report(stateDiagnostics.UNKNOWN_STATE_USAGE(node.value), { + node: context.rule, word: node.value, }); } @@ -655,7 +639,7 @@ export class StylableTransformer { const resolvedSymbols = this.getResolvedSymbols(meta); for (const [className, resolved] of Object.entries(resolvedSymbols.class)) { if (resolved.length > 1) { - meta.outputAst!.walkRules( + meta.targetAst!.walkRules( '.' + namespaceEscape(className, meta.namespace), (rule) => { const a = resolved[0]; @@ -695,14 +679,21 @@ function validateScopes(transformer: StylableTransformer, meta: StylableMeta) { ); const ruleReports = transformer.diagnostics.reports.splice(len); - ruleReports.forEach(({ message, type, options: { word } = {} }) => { - if (type === 'error') { - transformer.diagnostics.error(scope, message, { word: word || scope.params }); - } else { - transformer.diagnostics.warn(scope, message, { word: word || scope.params }); - } - }); + for (const { code, message, severity, word } of ruleReports) { + transformer.diagnostics.report( + { + code, + message, + severity, + }, + { + node: scope, + word: word || scope.params, + } + ); + } } + return transformedScopes; } @@ -836,3 +827,29 @@ export class ScopeContext { return ctx; } } + +/** + * in the process of moving transformations that shouldn't be in the analyzer. + * all changes were moved here to be called at the beginning of the transformer, + * and should be inlined in the process in the future. + */ +function prepareAST(context: FeatureTransformContext, ast: postcss.Root) { + // ToDo: inline transformations + const toRemove: Array void)> = []; + ast.walk((node) => { + const input = { context, node, toRemove }; + // namespace + if (node.type === 'atrule' && node.name === `namespace`) { + toRemove.push(node); + } + // extracted features + STImport.hooks.prepareAST(input); + STScope.hooks.prepareAST(input); + STVar.hooks.prepareAST(input); + STCustomSelector.hooks.prepareAST(input); + CSSCustomProperty.hooks.prepareAST(input); + }); + for (const removeOrNode of toRemove) { + typeof removeOrNode === 'function' ? removeOrNode() : removeOrNode.remove(); + } +} diff --git a/packages/core/src/stylable-utils.ts b/packages/core/src/stylable-utils.ts index 4430eb3aa..173f2caff 100644 --- a/packages/core/src/stylable-utils.ts +++ b/packages/core/src/stylable-utils.ts @@ -1,49 +1,22 @@ import { isAbsolute } from 'path'; import type * as postcss from 'postcss'; -import { replaceRuleSelector } from './replace-rule-selector'; -import type { Diagnostics } from './diagnostics'; -import type { Imported, ImportSymbol, StylableSymbol } from './features'; +import { createDiagnosticReporter, Diagnostics } from './diagnostics'; +import type { ImportSymbol, StylableSymbol } from './features'; import { isChildOfAtRule } from './helpers/rule'; import { scopeNestedSelector, parseSelectorWithCache } from './helpers/selector'; -export const CUSTOM_SELECTOR_RE = /:--[\w-]+/g; - export function isValidDeclaration(decl: postcss.Declaration) { return typeof decl.value === 'string'; } -export function expandCustomSelectors( - rule: postcss.Rule, - customSelectors: Record, - diagnostics?: Diagnostics -): string { - if (rule.selector.includes(':--')) { - rule.selector = rule.selector.replace( - CUSTOM_SELECTOR_RE, - (extensionName, _matches, selector) => { - if (!customSelectors[extensionName] && diagnostics) { - diagnostics.warn(rule, `The selector '${rule.selector}' is undefined`, { - word: rule.selector, - }); - return selector; - } - // TODO: support nested CustomSelectors - return ':matches(' + customSelectors[extensionName] + ')'; - } - ); - - return (rule.selector = transformMatchesOnRule(rule, false)); - } - return rule.selector; -} - -export function transformMatchesOnRule(rule: postcss.Rule, lineBreak: boolean) { - return replaceRuleSelector(rule, { lineBreak }); -} - -export const INVALID_MERGE_OF = (mergeValue: string) => { - return `invalid merge of: \n"${mergeValue}"`; +export const utilDiagnostics = { + INVALID_MERGE_OF: createDiagnosticReporter( + '14001', + 'error', + (mergeValue: string) => `invalid merge of: \n"${mergeValue}"` + ), }; + // ToDo: move to helpers/mixin export function mergeRules( mixinAst: postcss.Root, @@ -97,7 +70,9 @@ export function mergeRules( } nextRule = node; } else { - report?.warn(rule, INVALID_MERGE_OF(node.toString())); + report?.report(utilDiagnostics.INVALID_MERGE_OF(node.toString()), { + node: rule, + }); } } }); @@ -106,15 +81,20 @@ export function mergeRules( return rule; } -export function findDeclaration(importNode: Imported, test: any) { - const fromIndex = importNode.rule.nodes.findIndex(test); - return importNode.rule.nodes[fromIndex] as postcss.Declaration; -} +export const sourcePathDiagnostics = { + MISSING_SOURCE_FILENAME: createDiagnosticReporter( + '17001', + 'error', + () => 'missing source filename' + ), +}; export function getSourcePath(root: postcss.Root, diagnostics: Diagnostics) { const source = (root.source && root.source.input.file) || ''; if (!source) { - diagnostics.error(root, 'missing source filename'); + diagnostics.report(sourcePathDiagnostics.MISSING_SOURCE_FILENAME(), { + node: root, + }); } else if (!isAbsolute(source)) { throw new Error('source filename is not absolute path: "' + source + '"'); } @@ -130,8 +110,3 @@ export function getAlias(symbol: StylableSymbol): ImportSymbol | undefined { return undefined; } - -export function isValidClassName(className: string) { - const test = /^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/g; // checks valid classname - return !!className.match(test); -} diff --git a/packages/core/src/stylable-value-parsers.ts b/packages/core/src/stylable-value-parsers.ts index 3ef69f052..8ae1deeda 100644 --- a/packages/core/src/stylable-value-parsers.ts +++ b/packages/core/src/stylable-value-parsers.ts @@ -27,10 +27,14 @@ export const SBTypesParsers = { { clone: true } ); if (!selector[0]) { - diagnostics.error(decl, CSSClass.diagnostics.EMPTY_ST_GLOBAL()); + diagnostics.report(CSSClass.diagnostics.EMPTY_ST_GLOBAL(), { + node: decl, + }); return; } else if (selector.length > 1) { - diagnostics.error(decl, CSSClass.diagnostics.UNSUPPORTED_MULTI_SELECTORS_ST_GLOBAL()); + diagnostics.report(CSSClass.diagnostics.UNSUPPORTED_MULTI_SELECTORS_ST_GLOBAL(), { + node: decl, + }); } return selector[0].nodes; }, diff --git a/packages/core/src/stylable.ts b/packages/core/src/stylable.ts index b684b3079..69e6c4398 100644 --- a/packages/core/src/stylable.ts +++ b/packages/core/src/stylable.ts @@ -14,19 +14,15 @@ import { } from './stylable-transformer'; import type { IStylableOptimizer, ModuleResolver } from './types'; import { createDefaultResolver } from './module-resolver'; -import { warnOnce } from './helpers/deprecation'; -import { STVar, CSSCustomProperty } from './features'; +import { STImport, STScope, STVar, STMixin, CSSClass, CSSCustomProperty } from './features'; +import { Dependency, visitMetaCSSDependencies } from './visit-meta-css-dependencies'; import * as postcss from 'postcss'; export interface StylableConfig { projectRoot: string; fileSystem: MinimalFS; requireModule?: (path: string) => any; - /** @deprecated */ - delimiter?: string; onProcess?: (meta: StylableMeta, path: string) => StylableMeta; - /** @deprecated */ - diagnostics?: Diagnostics; hooks?: TransformHooks; resolveOptions?: { alias?: any; @@ -50,61 +46,67 @@ interface InitCacheParams { export type CreateProcessorOptions = Pick; export class Stylable { - public static create(config: StylableConfig) { - return new this( - config.projectRoot, - config.fileSystem, - (id) => { - if (config.requireModule) { - return config.requireModule(id); - } - throw new Error('Javascript files are not supported without requireModule options'); - }, - config.delimiter, - config.onProcess, - config.diagnostics, - config.hooks, - config.resolveOptions, - config.optimizer, - config.mode, - config.resolveNamespace, - config.resolveModule, - config.cssParser, - config.resolverCache, - config.fileProcessorCache - ); - } public fileProcessor: FileProcessor; public resolver: StylableResolver; + public stModule = new STImport.StylablePublicApi(this); + public stScope = new STScope.StylablePublicApi(this); public stVar = new STVar.StylablePublicApi(this); - constructor( - public projectRoot: string, - protected fileSystem: MinimalFS, - protected requireModule: (path: string) => any, - public delimiter: string = '__', - protected onProcess?: (meta: StylableMeta, path: string) => StylableMeta, - protected diagnostics = new Diagnostics(), - protected hooks: TransformHooks = {}, - protected resolveOptions: any = {}, - public optimizer?: IStylableOptimizer, - protected mode: 'production' | 'development' = 'production', - public resolveNamespace?: typeof processNamespace, - private moduleResolver: ModuleResolver = createDefaultResolver(fileSystem, resolveOptions), - protected cssParser: CssParser = cssParse, - protected resolverCache?: StylableResolverCache, // ToDo: v5 default to `new Map()` - // This cache is fragile and should be fresh if onProcess/resolveNamespace/cssParser is different - protected fileProcessorCache?: Record> - ) { + public stMixin = new STMixin.StylablePublicApi(this); + public cssClass = new CSSClass.StylablePublicApi(this); + // + public projectRoot: string; + protected fileSystem: MinimalFS; + protected requireModule: (path: string) => any; + protected onProcess?: (meta: StylableMeta, path: string) => StylableMeta; + protected diagnostics = new Diagnostics(); + protected hooks: TransformHooks; + protected resolveOptions: any; + public optimizer?: IStylableOptimizer; + protected mode: 'production' | 'development'; + public resolveNamespace?: typeof processNamespace; + public moduleResolver: ModuleResolver; + protected cssParser: CssParser; + protected resolverCache?: StylableResolverCache; + // This cache is fragile and should be fresh if onProcess/resolveNamespace/cssParser is different + protected fileProcessorCache?: Record>; + constructor(config: StylableConfig) { + this.projectRoot = config.projectRoot; + this.fileSystem = config.fileSystem; + this.requireModule = + config.requireModule || + (() => { + throw new Error('Javascript files are not supported without requireModule options'); + }); + this.onProcess = config.onProcess; + this.hooks = config.hooks || {}; + this.resolveOptions = config.resolveOptions || {}; + this.optimizer = config.optimizer; + this.mode = config.mode || `production`; + this.resolveNamespace = config.resolveNamespace; + this.moduleResolver = + config.resolveModule || createDefaultResolver(this.fileSystem, this.resolveOptions); + this.cssParser = config.cssParser || cssParse; + this.resolverCache = config.resolverCache; // ToDo: v5 default to `new Map()` + this.fileProcessorCache = config.fileProcessorCache; this.fileProcessor = createStylableFileProcessor({ - fileSystem, - onProcess, + fileSystem: this.fileSystem, + onProcess: this.onProcess, resolveNamespace: this.resolveNamespace, - cssParser, + cssParser: this.cssParser, cache: this.fileProcessorCache, }); this.resolver = this.createResolver(); } + public getDependencies(meta: StylableMeta) { + const dependencies: Dependency[] = []; + + for (const dependency of visitMetaCSSDependencies({ meta, resolver: this.resolver })) { + dependencies.push(dependency); + } + + return dependencies; + } public initCache({ filter }: InitCacheParams = {}) { if (filter && this.resolverCache) { for (const [key, cacheEntity] of this.resolverCache) { @@ -133,13 +135,8 @@ export class Stylable { }: CreateProcessorOptions = {}) { return new StylableProcessor(new Diagnostics(), resolveNamespace); } - /**@deprecated */ - public createTransformer(options?: Partial) { - return this._createTransformer(options); - } - private _createTransformer(options: Partial = {}) { + private createTransformer(options: Partial = {}) { return new StylableTransformer({ - delimiter: this.delimiter, moduleResolver: this.moduleResolver, diagnostics: new Diagnostics(), fileProcessor: this.fileProcessor, @@ -151,21 +148,12 @@ export class Stylable { ...options, }); } - public transform(meta: StylableMeta): StylableResults; - public transform(source: string, resourcePath: string): StylableResults; public transform( - meta: string | StylableMeta, - resourcePath?: string, - options: Partial = {}, - processorOptions: CreateProcessorOptions = {} + pathOrMeta: string | StylableMeta, + options: Partial = {} ): StylableResults { - if (typeof meta === 'string') { - meta = this.createProcessor(processorOptions).process( - this.cssParser(meta, { from: resourcePath }) - ); - } - const transformer = this._createTransformer(options); - this.fileProcessor.add(meta.source, meta); + const meta = typeof pathOrMeta === `string` ? this.analyze(pathOrMeta) : pathOrMeta; + const transformer = this.createTransformer(options); return transformer.transform(meta); } public transformSelector( @@ -174,7 +162,7 @@ export class Stylable { options?: Partial ): { selector: string; resolved: ResolvedElement[][] } { const meta = typeof pathOrMeta === `string` ? this.analyze(pathOrMeta) : pathOrMeta; - const transformer = this._createTransformer(options); + const transformer = this.createTransformer(options); const r = transformer.scopeSelector(meta, selector, undefined, undefined, true); return { selector: r.selector, @@ -205,20 +193,10 @@ export class Stylable { options?: Partial ): postcss.Root { const meta = typeof pathOrMeta === `string` ? this.analyze(pathOrMeta) : pathOrMeta; - const transformer = this._createTransformer(options); + const transformer = this.createTransformer(options); transformer.transformAst(ast, meta); return ast; } - /**@deprecated use stylable.analyze instead*/ - public process(fullPath: string, invalidateCache = false): StylableMeta { - warnOnce('Stylable.process is deprecated, please use stylable.analyze instead'); - if (typeof invalidateCache === 'string') { - warnOnce( - 'Stylable.process with context as second arguments is deprecated please resolve the fullPath with Stylable.resolvePath before using' - ); - } - return this.fileProcessor.process(fullPath, invalidateCache); - } public analyze(fullPath: string, overrideSrc?: string) { return overrideSrc ? this.fileProcessor.processContent(overrideSrc, fullPath) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 8d19f8649..6274dec4d 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,11 +1,7 @@ import type * as postcss from 'postcss'; import type { Box } from './custom-values'; -import type { StylableMeta } from './stylable-meta'; import type { StylableExports, StylableResults } from './stylable-transformer'; -export type PartialObject = Partial & object; -export type CSSObject = any & object; - export interface ParsedValue { type: string; value: string; @@ -41,46 +37,18 @@ export interface IStylableOptimizer { optimize( config: OptimizeConfig, stylableResult: StylableResults, - usageMapping: Record, - delimiter?: string + usageMapping: Record ): void; getNamespace(namespace: string): string; getClassName(className: string): string; optimizeAst( config: OptimizeConfig, - outputAst: postcss.Root, + targetAst: postcss.Root, usageMapping: Record, - delimiter: string | undefined, jsExports: StylableExports, globals: Record ): void; removeStylableDirectives(root: postcss.Root, shouldComment?: boolean): void; } -export interface IStylableClassNameOptimizer { - context: { - names: Record; - }; - rewriteSelector( - selector: string, - usageMapping: Record, - globals: Record - ): string; - generateName(name: string): string; - optimizeAstAndExports( - ast: postcss.Root, - exported: Record, - classes: string[], - usageMapping: Record, - globals?: Record - ): void; -} - -export interface IStylableNamespaceOptimizer { - index: number; - namespacePrefix: string; - namespaceMapping: Record; - getNamespace(meta: StylableMeta, ..._env: any[]): string; -} - export type ModuleResolver = (directoryPath: string, request: string) => string; diff --git a/packages/core/src/visit-meta-css-dependencies.ts b/packages/core/src/visit-meta-css-dependencies.ts index 62af901a7..902b184fe 100644 --- a/packages/core/src/visit-meta-css-dependencies.ts +++ b/packages/core/src/visit-meta-css-dependencies.ts @@ -2,12 +2,29 @@ import type { StylableMeta } from './stylable-meta'; import type { Imported } from './features'; import type { StylableResolver } from './stylable-resolver'; -export function visitMetaCSSDependenciesBFS( - meta: StylableMeta, - onMetaDependency: (meta: StylableMeta, imported: Imported, depth: number) => void, - resolver: StylableResolver, - onJsDependency?: (resolvedPath: string, imported: Imported) => void -): void { +export interface CSSDependency { + kind: 'css'; + resolvedPath: string; + imported: Imported; + depth: number; + meta: StylableMeta; +} + +export interface JSDependency { + kind: 'js'; + resolvedPath: string; + imported: Imported; +} + +export type Dependency = CSSDependency | JSDependency; + +export function* visitMetaCSSDependencies({ + meta, + resolver, +}: { + meta: StylableMeta; + resolver: StylableResolver; +}) { const visited = new Set([meta.source]); const q = [[...meta.getImportStatements()]]; let depth = -1; @@ -23,14 +40,29 @@ export function visitMetaCSSDependenciesBFS( if (res?._kind === 'css' && !visited.has(res.meta.source)) { visited.add(res.meta.source); - onMetaDependency(res.meta, imported, depth + 1); + const dependency: CSSDependency = { + kind: 'css', + depth: depth + 1, + meta: res.meta, + resolvedPath: res.meta.source, + imported, + }; + + yield dependency; + q[depth + 1].push(...res.meta.getImportStatements()); - } else if (res?._kind === 'js' && onJsDependency) { + } else if (res?._kind === 'js') { const resolvedPath = resolver.resolvePath(imported.context, imported.request); if (!visited.has(resolvedPath)) { visited.add(resolvedPath); - onJsDependency(resolvedPath, imported); + const dependency: JSDependency = { + kind: 'js', + imported, + resolvedPath, + }; + + yield dependency; } } } diff --git a/packages/core/test/arguement-parser.spec.ts b/packages/core/test/arguement-parser.spec.ts index 3f35883cd..e8707bce3 100644 --- a/packages/core/test/arguement-parser.spec.ts +++ b/packages/core/test/arguement-parser.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import postcssValueParser from 'postcss-value-parser'; -import { getFormatterArgs } from '@stylable/core'; +import { getFormatterArgs } from '@stylable/core/dist/helpers/value'; function test( desc: string, @@ -12,8 +12,8 @@ function test( it(desc, () => { const actualWarnings: string[] = []; const [firstNode] = postcssValueParser(src).nodes; - const formatterArgs = getFormatterArgs(firstNode, allowComments, (msg) => - actualWarnings.push(msg) + const formatterArgs = getFormatterArgs(firstNode, allowComments, (diag) => + actualWarnings.push(diag.message) ); expect(formatterArgs).to.eql(expected); if (expectedWarnings) { diff --git a/packages/core/test/cached-process-file.spec.ts b/packages/core/test/cached-process-file.spec.ts index 8981aa039..e2f23682d 100644 --- a/packages/core/test/cached-process-file.spec.ts +++ b/packages/core/test/cached-process-file.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { cachedProcessFile } from '@stylable/core'; +import { cachedProcessFile } from '@stylable/core/dist/index-internal'; describe('cachedProcessFile', () => { it('return process file content', () => { diff --git a/packages/core/test/custom-selectors.spec.ts b/packages/core/test/custom-selectors.spec.ts index 65149a206..ef0431b81 100644 --- a/packages/core/test/custom-selectors.spec.ts +++ b/packages/core/test/custom-selectors.spec.ts @@ -1,21 +1,24 @@ import { expect } from 'chai'; import type * as postcss from 'postcss'; import { generateStylableRoot, processSource } from '@stylable/core-test-kit'; +import { STCustomSelector } from '@stylable/core/dist/features'; describe('@custom-selector', () => { it('collect custom-selectors', () => { const from = '/path/to/style.css'; - const { customSelectors } = processSource( + const meta = processSource( ` @custom-selector :--icon .root > .icon; `, { from } ); - expect(customSelectors[':--icon']).to.equal('.root > .icon'); + const iconSelector = STCustomSelector.getCustomSelectorExpended(meta, 'icon'); + + expect(iconSelector).to.equal('.root > .icon'); }); - it('expand custom-selector before process (reflect on ast)', () => { + it('analyze custom-selector before process (reflect on ast)', () => { const from = '/path/to/style.css'; const meta = processSource( ` @@ -27,20 +30,6 @@ describe('@custom-selector', () => { { from } ); - const [rule] = meta.ast.nodes as [postcss.Rule]; - expect(rule.selector).to.equal('.root > .icon, .class'); - expect(meta.getClass(`icon`)).to.contain({ _kind: 'class', name: 'icon' }); - }); - - it('expand custom-selector before process (reflect on ast when not written)', () => { - const from = '/path/to/style.css'; - const meta = processSource( - ` - @custom-selector :--icon .root > .icon; - `, - { from } - ); - expect(meta.getClass(`icon`)).to.contain({ _kind: 'class', name: 'icon' }); }); diff --git a/packages/core/test/deprecated/deprecated-selector-utils.spec.ts b/packages/core/test/deprecated/deprecated-selector-utils.spec.ts deleted file mode 100644 index c3d5c570c..000000000 --- a/packages/core/test/deprecated/deprecated-selector-utils.spec.ts +++ /dev/null @@ -1,218 +0,0 @@ -import { expect } from 'chai'; -import { - matchSelectorTarget, - filterChunkNodesByType, - separateChunks, - parseSelector, - SelectorChunk, -} from '@stylable/core/dist/deprecated/deprecated-selector-utils'; - -describe('deprecated/selector-utils', () => { - describe('matchSelectorTarget', () => { - it('source should be composed of only one compound selector', () => { - expect(() => matchSelectorTarget('.x,.menu::button', '.x')).to.throw( - 'source selector must not be composed of more than one compound selector' - ); - }); - - it('should return true if requesting selector is contained in target selector', () => { - expect(matchSelectorTarget('.menu::button', '.x .menu:hover::button'), '1').to.equal( - true - ); - expect(matchSelectorTarget('.x .menu::button', '.menu::button::hover'), '2').to.equal( - false - ); - - expect(matchSelectorTarget('.menu::button', '.button'), '3').to.equal(false); - expect(matchSelectorTarget('.menu::button', '.menu'), '4').to.equal(false); - expect(matchSelectorTarget('.menu', '.menu::button'), '5').to.equal(false); - }); - - it('should not match empty requested selector in emptyly', () => { - expect(matchSelectorTarget('', '.menu::button')).to.equal(false); - }); - - it('should compare node types when comparing', () => { - expect(matchSelectorTarget('.x::y', '.x::y'), '1').to.equal(true); - expect(matchSelectorTarget('.x::y', '.x.y'), '2').to.equal(false); - expect(matchSelectorTarget('.a::a', '.a.a'), '3').to.equal(false); - expect(matchSelectorTarget('.a::a', '.a::a'), '4').to.equal(true); - }); - - it('should support multiple compound selectors', () => { - expect(matchSelectorTarget('.x', '.y,.x')).to.equal(true); - expect(matchSelectorTarget('.x', '.y,.z')).to.equal(false); - }); - - it('should regard order', () => { - expect(matchSelectorTarget('.x::y', '.y::x')).to.equal(false); - }); - - it('should not match if end is different', () => { - expect(matchSelectorTarget('.x::y::z', '.x::y::k')).to.equal(false); - }); - - it('should group by classes', () => { - expect(matchSelectorTarget('.x::y', '.x::y.z'), '1').to.equal(true); - expect(matchSelectorTarget('.x::y', '.x::y::z'), '2').to.equal(false); - expect(matchSelectorTarget('.x', '.x.z'), '3').to.equal(true); - }); - - it('should filter duplicate classes', () => { - expect(matchSelectorTarget('.x.x::y.z', '.x::y.z'), '1').to.equal(true); - expect(matchSelectorTarget('.x::y.x.z', '.x::y.z'), '2').to.equal(true); - expect(matchSelectorTarget('.x::y.x.x.x::z.z', '.x::y'), '3').to.equal(false); - expect(matchSelectorTarget('.x.x.x::y.z', '.x::y.z'), '4').to.equal(true); - }); - }); - - describe('filterChunkNodesByType', () => { - it('should filter and return only selector nodes which match types specified in array', () => { - expect( - filterChunkNodesByType({ nodes: [{ name: '0', type: 'a' }], type: 'dont-care' }, [ - 'a', - ]) - ).to.eql([ - { - name: '0', - type: 'a', - }, - ]); - expect( - filterChunkNodesByType( - { - nodes: [ - { name: '0', type: 'a' }, - { name: '1', type: 'b' }, - { name: '2', type: 'c' }, - ], - type: 'dont-care', - }, - ['b', 'a'] - ) - ).to.eql([ - { name: '0', type: 'a' }, - { name: '1', type: 'b' }, - ]); - }); - }); - - describe('separateChunks', () => { - const seperateChunksTestVectors: Array<{ - title: string; - selector: string; - expected: SelectorChunk[][]; - }> = [ - { - title: 'empty selector', - selector: '', - expected: [[{ type: 'selector', nodes: [] }]], - }, - { - title: 'class in first chunk', - selector: '.x', - expected: [ - [ - { - type: 'selector', - nodes: [{ type: 'class', name: 'x' }], - }, - ], - ], - }, - { - title: 'handle spacing', - selector: '.x .y', - expected: [ - [ - { - type: 'selector', - nodes: [{ type: 'class', name: 'x' }], - }, - { - type: 'spacing', - value: ' ', - nodes: [{ type: 'class', name: 'y' }], - }, - ], - ], - }, - { - title: 'handle operator', - selector: '.x + .y', - expected: [ - [ - { - type: 'selector', - nodes: [{ type: 'class', name: 'x' }], - }, - { - type: 'operator', - operator: '+', - nodes: [{ type: 'class', name: 'y' }], - }, - ], - ], - }, - { - title: 'handle multiple selector', - selector: '.x, .y', - expected: [ - [ - { - type: 'selector', - nodes: [{ type: 'class', name: 'x' }], - }, - ], - [ - { - type: 'selector', - nodes: [{ type: 'class', name: 'y' }], - }, - ], - ], - }, - { - title: 'handle chunks with several nodes', - selector: '.x, .y::z', - expected: [ - [ - { - type: 'selector', - nodes: [{ type: 'class', name: 'x' }], - }, - ], - [ - { - type: 'selector', - nodes: [ - { type: 'class', name: 'y' }, - { type: 'pseudo-element', name: 'z' }, - ], - }, - ], - ], - }, - { - title: 'handle 2 selectors', - selector: '.x.y', - expected: [ - [ - { - type: 'selector', - nodes: [ - { type: 'class', name: 'x' }, - { type: 'class', name: 'y' }, - ], - }, - ], - ], - }, - ]; - seperateChunksTestVectors.forEach((test) => { - it(test.title, () => { - expect(separateChunks(parseSelector(test.selector))).to.eql(test.expected); - }); - }); - }); -}); diff --git a/packages/core/test/deprecated/deprecated-stylable-utils.spec.ts b/packages/core/test/deprecated/deprecated-stylable-utils.spec.ts deleted file mode 100644 index d57855f75..000000000 --- a/packages/core/test/deprecated/deprecated-stylable-utils.spec.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { expect } from 'chai'; -import { - createSubsetAst, - scopeSelector, -} from '@stylable/core/dist/deprecated/deprecated-stylable-utils'; -import { cssParse } from '@stylable/core'; - -describe('deprecated/selector-utils', () => { - describe('scopeSelector', () => { - const tests: Array<{ root: string; child: string; selector: string; only?: boolean }> = [ - { - root: '.a', - child: '.x', - selector: '.a .x', - }, - { - root: '.a', - child: '.x:hover', - selector: '.a .x:hover', - }, - { - root: '.a', - child: '&', - selector: '.a', - }, - { - root: '.a:hover', - child: '&', - selector: '.a:hover', - }, - { - root: '.a.x', - child: '&', - selector: '.a.x', - }, - { - root: '.a.x .b:hover', - child: '&', - selector: '.a.x .b:hover', - }, - { - root: '.a', - child: '&.x', - selector: '.a.x', - }, - { - root: '.a', - child: '&.x .y', - selector: '.a.x .y', - }, - { - root: '.a .b', - child: '&.x .y', - selector: '.a .b.x .y', - }, - { - root: '.a', - child: '& &', - selector: '.a .a', - }, - { - root: '.a, .b', - child: '& & &', - selector: '.a .a .a, .b .b .b', - }, - { - root: '.a:hover, .b:focus', - child: '& & &', - selector: '.a:hover .a:hover .a:hover, .b:focus .b:focus .b:focus', - }, - { - root: '.a', - child: ':global(.x) &', - selector: ':global(.x) .a', - }, - ]; - - for (const { only, root, selector, child } of tests) { - const test = only ? it.only : it; - test(`apply "${root}" on "${child}" should output "${selector}"`, () => { - const res = scopeSelector(root, child); - expect(res.selector).to.equal(selector); - }); - } - }); - describe('createSubsetAst', () => { - function testMatcher(expected: any[], actualNodes: any[]) { - expected.forEach((expectedMatch, i) => { - const { nodes, ...match } = expectedMatch; - const actual = actualNodes[i]; - expect(actual).to.contain(match); - if (nodes) { - testMatcher(nodes, actual.nodes); - } - }); - expect(actualNodes.length).to.equal(expected.length); - } - - it('should extract all selectors that has given prefix in the first chunk', () => { - const res = createSubsetAst( - cssParse(` - .i .x{} - .i::x{} - .i[data]{} - .i:hover{} - .x,.i{} - .i,.x{} - .i.x{} - .x.i{} - - /*more complex*/ - .x.y::i.z:hover.i{} - .x,.i:hover .y{} - .i .y,.x{} - .i:not(.x){} - .i .x:hover.i{} - .x.i.y{} - - .i.i{} - - /*extracted as decl on root*/ - .i{color: red} - - /*not extracted*/ - .x .i{} - :not(.i) .i{} - `), - '.i' - ); - - const expected = [ - { selector: '& .x' }, - { selector: '&::x' }, - { selector: '&[data]' }, - { selector: '&:hover' }, - { selector: '&' }, - { selector: '&' }, - { selector: '&.x' }, - { selector: '&.x' }, - { selector: '&.x.y::i.z:hover' }, - { selector: '&:hover .y' }, - { selector: '& .y' }, - { selector: '&:not(.x)' }, - { selector: '& &.x:hover' }, - { selector: '&.x.y' }, - { selector: '&&' }, // TODO: check if possible - { selector: '&' }, - ]; - - testMatcher(expected, res.nodes); - }); - - it('should extract global when creating root chunk', () => { - const res = createSubsetAst( - cssParse(` - :global(.x){} - :global(.x) .root{} - `), - '.root', - undefined, - true - ); - - const expected = [{ selector: ':global(.x)' }, { selector: ':global(.x) &' }]; - - testMatcher(expected, res.nodes); - }); - - it('should parts under @media', () => { - const res = createSubsetAst( - cssParse(` - .i {color: red} - .i:hover {} - @media (max-width: 300px) { - .i {} - .i:hover {} - } - `), - '.i' - ); - - const expected = [ - { selector: '&' }, - { selector: '&:hover' }, - { - type: 'atrule', - params: '(max-width: 300px)', - nodes: [{ selector: '&' }, { selector: '&:hover' }], - }, - ]; - - testMatcher(expected, res.nodes); - }); - - it('should not append empty media', () => { - const res = createSubsetAst( - cssParse(` - .i {} - @media (max-width: 300px) { - .x {} - } - `), - '.i' - ); - - const expected = [{ selector: '&' }]; - - testMatcher(expected, res.nodes); - }); - }); -}); diff --git a/packages/core/test/diagnostic-codes.spec.ts b/packages/core/test/diagnostic-codes.spec.ts new file mode 100644 index 000000000..bc0cfbc16 --- /dev/null +++ b/packages/core/test/diagnostic-codes.spec.ts @@ -0,0 +1,68 @@ +import { expect } from 'chai'; +import { + CSSClass, + CSSCustomProperty, + CSSKeyframes, + CSSType, + STGlobal, + STImport, + STMixin, + STSymbol, + STVar, + STCustomSelector, +} from '@stylable/core/dist/features'; +import { generalDiagnostics } from '@stylable/core/dist/features/diagnostics'; +import { atPropertyValidationWarnings } from '@stylable/core/dist/helpers/css-custom-property'; +import { parseImportMessages, ensureImportsMessages } from '@stylable/core/dist/helpers/import'; +import { mixinHelperDiagnostics } from '@stylable/core/dist/helpers/mixin'; +import { valueDiagnostics } from '@stylable/core/dist/helpers/value'; +import { functionDiagnostics } from '@stylable/core/dist/functions'; +import { stateDiagnostics } from '@stylable/core/dist/pseudo-states'; +import { processorDiagnostics } from '@stylable/core/dist/stylable-processor'; +import { transformerDiagnostics } from '@stylable/core/dist/stylable-transformer'; +import { utilDiagnostics, sourcePathDiagnostics } from '@stylable/core/dist/stylable-utils'; + +describe('diagnostics error codes', () => { + it('should assure all error codes are unique', () => { + const diags: Record = {}; + + const reporters = { + ...generalDiagnostics, + ...CSSClass.diagnostics, + ...CSSCustomProperty.diagnostics, + ...CSSKeyframes.diagnostics, + ...CSSType.diagnostics, + ...STGlobal.diagnostics, + ...STImport.diagnostics, + ...STMixin.diagnostics, + ...STSymbol.diagnostics, + ...STVar.diagnostics, + ...STCustomSelector.diagnostics, + ...atPropertyValidationWarnings, + ...parseImportMessages, + ...ensureImportsMessages, + ...mixinHelperDiagnostics, + ...valueDiagnostics, + ...functionDiagnostics, + ...stateDiagnostics, + ...processorDiagnostics, + ...transformerDiagnostics, + ...utilDiagnostics, + ...sourcePathDiagnostics, + }; + + let failingCode = ''; + + for (const [_name, func] of Object.entries(reporters)) { + const code = func.code; + + if (diags[code] && diags[code] !== func) { + failingCode = code; + } else { + diags[code] = func; + } + } + + expect(failingCode, 'duplicate error code detected').to.eql(''); + }); +}); diff --git a/packages/core/test/diagnostics.spec.ts b/packages/core/test/diagnostics.spec.ts index 48edc191b..81aae57d6 100644 --- a/packages/core/test/diagnostics.spec.ts +++ b/packages/core/test/diagnostics.spec.ts @@ -1,13 +1,24 @@ import { expect } from 'chai'; import { + diagnosticBankReportToStrings, expectAnalyzeDiagnostics, expectTransformDiagnostics, findTestLocations, } from '@stylable/core-test-kit'; -import { processorWarnings, transformerWarnings, nativePseudoElements } from '@stylable/core'; +import { + processorDiagnostics, + transformerDiagnostics, + nativePseudoElements, +} from '@stylable/core/dist/index-internal'; import { CSSClass, CSSType } from '@stylable/core/dist/features'; import { generalDiagnostics } from '@stylable/core/dist/features/diagnostics'; +const cssTypeDiagnostics = diagnosticBankReportToStrings(CSSType.diagnostics); +const cssClassDiagnostics = diagnosticBankReportToStrings(CSSClass.diagnostics); +const transformerStringDiagnostics = diagnosticBankReportToStrings(transformerDiagnostics); +const processorStringDiagnostics = diagnosticBankReportToStrings(processorDiagnostics); +const generalStringDiagnostics = diagnosticBankReportToStrings(generalDiagnostics); + describe('findTestLocations', () => { it('find single location 1', () => { const l = findTestLocations('\n |a|'); @@ -92,7 +103,7 @@ describe('diagnostics: warnings and errors', () => { expectAnalyzeDiagnostics(`|.root $#abc()$| {}`, [ { severity: `error`, - message: CSSType.diagnostics.INVALID_FUNCTIONAL_SELECTOR(`#abc`, `id`), + message: cssTypeDiagnostics.INVALID_FUNCTIONAL_SELECTOR(`#abc`, `id`), file: `main.css`, }, ]); @@ -101,7 +112,7 @@ describe('diagnostics: warnings and errors', () => { expectAnalyzeDiagnostics(`|.root $[attr]()$| {}`, [ { severity: `error`, - message: CSSType.diagnostics.INVALID_FUNCTIONAL_SELECTOR( + message: cssTypeDiagnostics.INVALID_FUNCTIONAL_SELECTOR( `[attr]`, `attribute` ), @@ -113,7 +124,7 @@ describe('diagnostics: warnings and errors', () => { expectAnalyzeDiagnostics(`|.root $&()$| {}`, [ { severity: `error`, - message: CSSType.diagnostics.INVALID_FUNCTIONAL_SELECTOR(`&`, `nesting`), + message: cssTypeDiagnostics.INVALID_FUNCTIONAL_SELECTOR(`&`, `nesting`), file: `main.css`, }, ]); @@ -226,7 +237,7 @@ describe('diagnostics: warnings and errors', () => { }; expectTransformDiagnostics(config, [ { - message: transformerWarnings.UNKNOWN_PSEUDO_ELEMENT('myBtn'), + message: transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT('myBtn'), file: '/main.css', }, ]); @@ -295,7 +306,9 @@ describe('diagnostics: warnings and errors', () => { [ { message: - generalDiagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR('-st-extends'), + generalStringDiagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR( + '-st-extends' + ), file: 'main.css', }, ] @@ -320,7 +333,10 @@ describe('diagnostics: warnings and errors', () => { `, [ { - message: processorWarnings.OVERRIDE_TYPED_RULE(`-st-extends`, 'root'), + message: processorStringDiagnostics.OVERRIDE_TYPED_RULE( + `-st-extends`, + 'root' + ), file: 'main.css', }, ] @@ -396,7 +412,12 @@ describe('diagnostics: warnings and errors', () => { |.$Blah$| {} `, - [{ message: CSSClass.diagnostics.UNSCOPED_CLASS('Blah'), file: 'main.css' }] + [ + { + message: cssClassDiagnostics.UNSCOPED_CLASS('Blah'), + file: 'main.css', + }, + ] ); }); @@ -444,7 +465,7 @@ describe('diagnostics: warnings and errors', () => { `, [ { - message: CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR('button'), + message: cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR('button'), file: 'main.css', }, ] @@ -458,7 +479,7 @@ describe('diagnostics: warnings and errors', () => { `, [ { - message: CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR('button'), + message: cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR('button'), file: 'main.css', }, ] diff --git a/packages/core/test/extend-function-parser.spec.ts b/packages/core/test/extend-function-parser.spec.ts index ff259007f..752e442ee 100644 --- a/packages/core/test/extend-function-parser.spec.ts +++ b/packages/core/test/extend-function-parser.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { SBTypesParsers } from '@stylable/core'; +import { SBTypesParsers } from '@stylable/core/dist/stylable-value-parsers'; const parseExtends = SBTypesParsers[`-st-extends`]; diff --git a/packages/core/test/features/css-class.spec.ts b/packages/core/test/features/css-class.spec.ts index 7f4825eb4..4b8888f69 100644 --- a/packages/core/test/features/css-class.spec.ts +++ b/packages/core/test/features/css-class.spec.ts @@ -1,8 +1,14 @@ import { STImport, CSSClass, STSymbol } from '@stylable/core/dist/features'; -import { ignoreDeprecationWarn } from '@stylable/core/dist/helpers/deprecation'; -import { testStylableCore, shouldReportNoDiagnostics } from '@stylable/core-test-kit'; +import { + testStylableCore, + shouldReportNoDiagnostics, + diagnosticBankReportToStrings, +} from '@stylable/core-test-kit'; import { expect } from 'chai'; +const classDiagnostics = diagnosticBankReportToStrings(CSSClass.diagnostics); +const stSymbolDiagnostics = diagnosticBankReportToStrings(STSymbol.diagnostics); + describe(`features/css-class`, () => { it(`should have root class`, () => { const { sheets } = testStylableCore({ @@ -116,20 +122,6 @@ describe(`features/css-class`, () => { expect(exports.classes.e, `e JS export`).to.eql(`entry__e`); expect(exports.classes.f, `f JS export`).to.eql(`entry__f`); expect(exports.classes.g, `g JS export`).to.eql(`entry__g`); - - // deprecation - ignoreDeprecationWarn(() => { - expect(meta.classes, `deprecated 'meta.classes'`).to.eql({ - root: CSSClass.get(meta, `root`), - a: CSSClass.get(meta, `a`), - b: CSSClass.get(meta, `b`), - c: CSSClass.get(meta, `c`), - d: CSSClass.get(meta, `d`), - e: CSSClass.get(meta, `e`), - f: CSSClass.get(meta, `f`), - g: CSSClass.get(meta, `g`), - }); - }); }); it(`should override with -st-global value`, () => { const { sheets } = testStylableCore(` @@ -202,13 +194,13 @@ describe(`features/css-class`, () => { const { sheets } = testStylableCore(` /* @rule(empty) .entry__a */ .a { - /* @analyze-error(empty) ${CSSClass.diagnostics.EMPTY_ST_GLOBAL()} */ + /* @analyze-error(empty) ${classDiagnostics.EMPTY_ST_GLOBAL()} */ -st-global: ""; } /* @rule(empty) .y */ .b { - /* @analyze-error(multi) ${CSSClass.diagnostics.UNSUPPORTED_MULTI_SELECTORS_ST_GLOBAL()} */ + /* @analyze-error(multi) ${classDiagnostics.UNSUPPORTED_MULTI_SELECTORS_ST_GLOBAL()} */ -st-global: ".y , .z"; } `); @@ -288,7 +280,7 @@ describe(`features/css-class`, () => { const { sheets } = testStylableCore(` /* @rule(functional class) .entry__a() - @analyze-error(functional class) ${CSSClass.diagnostics.INVALID_FUNCTIONAL_SELECTOR( + @analyze-error(functional class) ${classDiagnostics.INVALID_FUNCTIONAL_SELECTOR( `.a`, `class` )} @@ -390,7 +382,7 @@ describe(`features/css-class`, () => { /* @rule .entry__unknown - @transform-error(unresolved alias) word(unknown) ${CSSClass.diagnostics.UNKNOWN_IMPORT_ALIAS( + @transform-error(unresolved alias) word(unknown) ${classDiagnostics.UNKNOWN_IMPORT_ALIAS( `unknown` )} */ @@ -451,7 +443,7 @@ describe(`features/css-class`, () => { const { sheets } = testStylableCore({ '/other.st.css': ``, '/entry.st.css': ` - /* @analyze-warn ${STSymbol.diagnostics.REDECLARE_ROOT()} */ + /* @analyze-error ${stSymbolDiagnostics.REDECLARE_ROOT()} */ @st-import [root] from './other.st.css'; /* @rule .entry__root */ @@ -482,7 +474,7 @@ describe(`features/css-class`, () => { '/entry.st.css': ` @st-import [importedPart] from "./classes.st.css"; - /* @analyze-warn word(importedPart) ${CSSClass.diagnostics.UNSCOPED_CLASS( + /* @analyze-warn word(importedPart) ${classDiagnostics.UNSCOPED_CLASS( `importedPart` )} */ .importedPart {} @@ -755,19 +747,19 @@ describe(`features/css-class`, () => { @st-import [unknown, stColor] from './sheet.st.css'; .a { - /* @transform-error(javascript) word(JS) ${CSSClass.diagnostics.CANNOT_EXTEND_JS()} */ + /* @transform-error(javascript) word(JS) ${classDiagnostics.CANNOT_EXTEND_JS()} */ -st-extends: JS; } .b { - /* @transform-error(unresolved named) word(unknown) ${CSSClass.diagnostics.CANNOT_EXTEND_UNKNOWN_SYMBOL( + /* @transform-error(unresolved named) word(unknown) ${classDiagnostics.CANNOT_EXTEND_UNKNOWN_SYMBOL( `unknown` )} */ -st-extends: unknown; } .c { - /* @transform-error(unsupported symbol) word(stColor) ${CSSClass.diagnostics.IMPORT_ISNT_EXTENDABLE()} */ + /* @transform-error(unsupported symbol) word(stColor) ${classDiagnostics.IMPORT_ISNT_EXTENDABLE()} */ -st-extends: stColor; } `, @@ -776,7 +768,78 @@ describe(`features/css-class`, () => { }); describe(`css-pseudo-class`, () => { // ToDo: move to css-pseudo-class spec once feature is created + describe(`st-var`, () => { + it('should unsupported value() within var definition / call', () => { + const { sheets } = testStylableCore(` + :vars { + optionA: a; + optionB: b; + optionC: c; + } + + .root { + -st-states: + option(enum( + value(optionA), + value(optionB) + )) value(optionB); + } + + /* @rule(default) .entry__root.entry---option-1-b */ + .root:option {} + + /* @rule(target value) .entry__root.entry---option-1-a */ + .root:option(value(optionA)) {} + + /* + @x-transform-error(target invalid) invalid optionC + @rule(target invalid) .entry__root.entry---option-1-c + */ + .root:option(value(optionC)) {} + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); // ToDo: `target invalid` should report + }); + }); describe(`st-mixin`, () => { + it.skip('should override value() within var definition / call', () => { + // mixins could be able to gain more power by overriding st-var in state definitions and selectors + const { sheets } = testStylableCore(` + :vars { + optionA: a; + optionB: b; + optionC: c; + optionD: c; + } + + .mix { + -st-states: + option(enum( + value(optionA), + value(optionB) + )) value(optionB); + } + .mix:option {} + .mix:option(value(optionA)) {} + + /* + @rule[1](default) .entry__into.entry---option-1-d + @rule[2](target value) .entry__into.entry---option-1-c + */ + .into { + -st-mixin: mix( + optionA value(optionC), + optionB value(optionD) + ); + } + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); it(`should mix custom state`, () => { const { sheets } = testStylableCore({ '/base.st.css': ` @@ -1025,6 +1088,44 @@ describe(`features/css-class`, () => { }); }); describe(`stylable (public API)`, () => { + it(`should transform class name`, () => { + const { stylable, sheets } = testStylableCore({ + 'other.st.css': ` + .x {} + :global(.y) {} + .z { + -st-global: "[attr=z]"; + } + `, + 'entry.st.css': ` + @st-import [x as ext-x, y as ext-y, z as ext-z] from './other.st.css'; + .a {} + :global(.b) {} + .c { + -st-global: "[attr=c]"; + } + :vars { + not-a-class: red; + } + `, + }); + + const { meta } = sheets['/entry.st.css']; + const api = stylable.cssClass; + + // ToDo: fix :global(.class) not registering as symbol? + + expect(api.transformIntoSelector(meta, 'a'), 'local class').to.eql('.entry__a'); + // expect(api.transformIntoSelector(meta, 'b'), 'local global class').to.eql('.b'); + expect(api.transformIntoSelector(meta, 'c'), 'local mapped class').to.eql('[attr=c]'); + expect(api.transformIntoSelector(meta, 'unknown'), 'unknown class').to.eql(undefined); + expect(api.transformIntoSelector(meta, 'not-a-class'), 'not class').to.eql(undefined); + expect(api.transformIntoSelector(meta, 'ext-x'), 'imported class').to.eql('.other__x'); + // expect(api.transformIntoSelector(meta, 'ext-y'), 'imported global class').to.eql('.y'); + expect(api.transformIntoSelector(meta, 'ext-z'), 'imported mapped class').to.eql( + '[attr=z]' + ); + }); it(`should not modify globals when transforming external selector`, () => { const { stylable, sheets } = testStylableCore(` .a :global(.a) {} diff --git a/packages/core/test/features/css-custom-property.spec.ts b/packages/core/test/features/css-custom-property.spec.ts index 6a2062693..7d11e33a6 100644 --- a/packages/core/test/features/css-custom-property.spec.ts +++ b/packages/core/test/features/css-custom-property.spec.ts @@ -1,9 +1,16 @@ import { STImport, CSSCustomProperty, STSymbol } from '@stylable/core/dist/features'; -import { ignoreDeprecationWarn } from '@stylable/core/dist/helpers/deprecation'; import { generateScopedCSSVar } from '@stylable/core/dist/helpers/css-custom-property'; -import { testStylableCore, shouldReportNoDiagnostics } from '@stylable/core-test-kit'; +import { + testStylableCore, + shouldReportNoDiagnostics, + diagnosticBankReportToStrings, +} from '@stylable/core-test-kit'; import { expect } from 'chai'; +const stImportDiagnostics = diagnosticBankReportToStrings(STImport.diagnostics); +const stSymbolDiagnostics = diagnosticBankReportToStrings(STSymbol.diagnostics); +const customPropertyDiagnostics = diagnosticBankReportToStrings(CSSCustomProperty.diagnostics); + describe(`features/css-custom-property`, () => { it(`should process css declaration prop`, () => { const { sheets } = testStylableCore(` @@ -37,14 +44,6 @@ describe(`features/css-custom-property`, () => { // JS exports expect(exports.vars.propA, `propA JS export`).to.eql(`--entry-propA`); expect(exports.vars.propB, `propB JS export`).to.eql(`--entry-propB`); - - // deprecation - ignoreDeprecationWarn(() => { - expect(meta.cssVars, `deprecated 'meta.cssVars'`).to.eql({ - '--propA': CSSCustomProperty.get(meta, `--propA`), - '--propB': CSSCustomProperty.get(meta, `--propB`), - }); - }); }); it(`should process css declaration value var()`, () => { const { sheets } = testStylableCore(` @@ -131,10 +130,9 @@ describe(`features/css-custom-property`, () => { expect(exports.vars.propB, `propB JS export`).to.eql(`--entry-propB`); }); it(`should process conflicted definitions`, () => { - const symbolDiag = STSymbol.diagnostics; const { sheets } = testStylableCore(` /* @analyze-warn(@property conflicted) word(--conflicted) - ${symbolDiag.REDECLARE_SYMBOL(`--conflicted`)}*/ + ${stSymbolDiagnostics.REDECLARE_SYMBOL(`--conflicted`)}*/ @property --conflicted { syntax: ''; initial-value: green; @@ -148,7 +146,7 @@ describe(`features/css-custom-property`, () => { }; /* @analyze-warn(@property conflicted) word(--conflicted) - ${symbolDiag.REDECLARE_SYMBOL(`--conflicted`)}*/ + ${stSymbolDiagnostics.REDECLARE_SYMBOL(`--conflicted`)}*/ @property --conflicted { syntax: ''; initial-value: green; @@ -207,7 +205,7 @@ describe(`features/css-custom-property`, () => { testStylableCore(` /* @atrule(no-dashes) propY - @analyze-warn(no-dashes) word(propY) ${CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_USE( + @analyze-error(no-dashes) word(propY) ${customPropertyDiagnostics.ILLEGAL_CSS_VAR_USE( 'propY' )} */ @@ -219,7 +217,7 @@ describe(`features/css-custom-property`, () => { /* @atrule(no-dashes-global) st-global(propZ) - @analyze-warn(no-dashes-global) word(propZ) ${CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_USE( + @analyze-error(no-dashes-global) word(propZ) ${customPropertyDiagnostics.ILLEGAL_CSS_VAR_USE( 'propZ' )} */ @@ -232,7 +230,7 @@ describe(`features/css-custom-property`, () => { .decls { /* @decl(empty var) prop: var() - @analyze-warn(empty var) ${CSSCustomProperty.diagnostics.MISSING_PROP_NAME()} + @analyze-error(empty var) ${customPropertyDiagnostics.MISSING_PROP_NAME()} */ prop: var(); } @@ -240,7 +238,7 @@ describe(`features/css-custom-property`, () => { .root { /* @decl(no-dashes) prop: var(propA) - @analyze-warn(no-dashes) word(propA) ${CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_USE( + @analyze-error(no-dashes) word(propA) ${customPropertyDiagnostics.ILLEGAL_CSS_VAR_USE( 'propA' )} */ @@ -248,7 +246,7 @@ describe(`features/css-custom-property`, () => { /* @decl(space+text) prop: var(--entry-propB notAllowed, fallback) - @analyze-warn(space+text) word(--propB notAllowed, fallback) ${CSSCustomProperty.diagnostics.ILLEGAL_CSS_VAR_ARGS( + @analyze-error(space+text) word(--propB notAllowed, fallback) ${customPropertyDiagnostics.ILLEGAL_CSS_VAR_ARGS( '--propB notAllowed, fallback' )} */ @@ -272,7 +270,7 @@ describe(`features/css-custom-property`, () => { describe(`@property validation`, () => { it(`should report on missing syntax`, () => { const { sheets } = testStylableCore(` - /* @analyze-warn(syntax) word(--a) ${CSSCustomProperty.diagnostics.MISSING_REQUIRED_DESCRIPTOR( + /* @analyze-error(syntax) word(--a) ${customPropertyDiagnostics.MISSING_REQUIRED_DESCRIPTOR( 'syntax' )} */ @property --a { @@ -280,7 +278,7 @@ describe(`features/css-custom-property`, () => { initial-value: #c0ffee; } - /* @analyze-warn(inherits) word(--b) ${CSSCustomProperty.diagnostics.MISSING_REQUIRED_DESCRIPTOR( + /* @analyze-error(inherits) word(--b) ${customPropertyDiagnostics.MISSING_REQUIRED_DESCRIPTOR( 'inherits' )} */ @property --b { @@ -288,7 +286,7 @@ describe(`features/css-custom-property`, () => { initial-value: #c0ffee; } - /* @analyze-warn(inherits) word(--c) ${CSSCustomProperty.diagnostics.MISSING_REQUIRED_INITIAL_VALUE_DESCRIPTOR()} */ + /* @analyze-warn(inherits) word(--c) ${customPropertyDiagnostics.MISSING_REQUIRED_INITIAL_VALUE_DESCRIPTOR()} */ @property --c { syntax: ''; inherits: false; @@ -311,7 +309,7 @@ describe(`features/css-custom-property`, () => { syntax: '*'; inherits: false; - /* @analyze-warn(atrule) word(abc) ${CSSCustomProperty.diagnostics.INVALID_DESCRIPTOR_TYPE( + /* @analyze-error(atrule) word(abc) ${customPropertyDiagnostics.INVALID_DESCRIPTOR_TYPE( 'atrule' )} */ @some-at-rule abc{} @@ -321,7 +319,7 @@ describe(`features/css-custom-property`, () => { syntax: '*'; inherits: false; - /* @analyze-warn(rule) word(div) ${CSSCustomProperty.diagnostics.INVALID_DESCRIPTOR_TYPE( + /* @analyze-error(rule) word(div) ${customPropertyDiagnostics.INVALID_DESCRIPTOR_TYPE( 'rule' )} */ div {} @@ -334,7 +332,7 @@ describe(`features/css-custom-property`, () => { syntax: '*'; inherits: false; - /* @analyze-warn word(initialValue) ${CSSCustomProperty.diagnostics.INVALID_DESCRIPTOR_NAME( + /* @analyze-error word(initialValue) ${customPropertyDiagnostics.INVALID_DESCRIPTOR_NAME( 'initialValue' )} */ initialValue: red; @@ -345,10 +343,10 @@ describe(`features/css-custom-property`, () => { describe(`@st-global-custom-property (deprecated)`, () => { it(`should mark properties as global`, () => { testStylableCore(` - /* @analyze-info(first) ${CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY()}*/ + /* @analyze-info(first) ${customPropertyDiagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY()}*/ @st-global-custom-property --x; - /* @analyze-info(second) ${CSSCustomProperty.diagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY()}*/ + /* @analyze-info(second) ${customPropertyDiagnostics.DEPRECATED_ST_GLOBAL_CUSTOM_PROPERTY()}*/ @st-global-custom-property --a ,--b, --c , --d ; @@ -362,10 +360,9 @@ describe(`features/css-custom-property`, () => { `); }); it(`should conflict with @property - and override global definition`, () => { - const symbolDiag = STSymbol.diagnostics; const { sheets } = testStylableCore(` /* @analyze-warn(@property before) word(--before) - ${symbolDiag.REDECLARE_SYMBOL(`--before`)}*/ + ${stSymbolDiagnostics.REDECLARE_SYMBOL(`--before`)}*/ @property --before { syntax: ''; initial-value: green; @@ -373,13 +370,17 @@ describe(`features/css-custom-property`, () => { }; /* - @analyze-warn(before) word(--before) ${symbolDiag.REDECLARE_SYMBOL(`--before`)} - @analyze-warn(after) word(--after) ${symbolDiag.REDECLARE_SYMBOL(`--after`)} + @analyze-warn(before) word(--before) ${stSymbolDiagnostics.REDECLARE_SYMBOL( + `--before` + )} + @analyze-warn(after) word(--after) ${stSymbolDiagnostics.REDECLARE_SYMBOL( + `--after` + )} */ @st-global-custom-property --before, --after; /* @analyze-warn(@property after) word(--after) - ${symbolDiag.REDECLARE_SYMBOL(`--after`)}*/ + ${stSymbolDiagnostics.REDECLARE_SYMBOL(`--after`)}*/ @property --after{ syntax: ''; initial-value: green; @@ -404,7 +405,7 @@ describe(`features/css-custom-property`, () => { testStylableCore(` /* @transform-remove(no-dashes) - @analyze-warn(no-dashes) word(propA) ${CSSCustomProperty.diagnostics.ILLEGAL_GLOBAL_CSS_VAR( + @analyze-error(no-dashes) word(propA) ${customPropertyDiagnostics.ILLEGAL_GLOBAL_CSS_VAR( 'propA' )} */ @@ -412,7 +413,7 @@ describe(`features/css-custom-property`, () => { /* @transform-remove(missing comma) - @analyze-warn(missing comma) word(--propB --propC) ${CSSCustomProperty.diagnostics.GLOBAL_CSS_VAR_MISSING_COMMA( + @analyze-error(missing comma) word(--propB --propC) ${customPropertyDiagnostics.GLOBAL_CSS_VAR_MISSING_COMMA( '--propB --propC' )} */ @@ -515,20 +516,20 @@ describe(`features/css-custom-property`, () => { } `, '/entry.st.css': ` - /* @analyze-warn(before) ${STSymbol.diagnostics.REDECLARE_SYMBOL(`--before`)} */ + /* @analyze-warn(before) ${stSymbolDiagnostics.REDECLARE_SYMBOL(`--before`)} */ @property --before; /* - @analyze-warn(imported before) word(--before) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + @analyze-warn(imported before) word(--before) ${stSymbolDiagnostics.REDECLARE_SYMBOL( `--before` )} - @analyze-warn(imported after) word(--after) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + @analyze-warn(imported after) word(--after) ${stSymbolDiagnostics.REDECLARE_SYMBOL( `--after` )} */ @st-import [--before, --after] from "./props.st.css"; - /* @analyze-warn(after) ${STSymbol.diagnostics.REDECLARE_SYMBOL(`--after`)} */ + /* @analyze-warn(after) ${stSymbolDiagnostics.REDECLARE_SYMBOL(`--after`)} */ @property --after; .root { @@ -628,7 +629,7 @@ describe(`features/css-custom-property`, () => { const { sheets } = testStylableCore({ '/props.st.css': ``, '/entry.st.css': ` - /* @transform-warn word(--unknown) ${STImport.diagnostics.UNKNOWN_IMPORTED_SYMBOL( + /* @transform-error word(--unknown) ${stImportDiagnostics.UNKNOWN_IMPORTED_SYMBOL( '--unknown', './props.st.css' )} */ diff --git a/packages/core/test/features/css-keyframes.spec.ts b/packages/core/test/features/css-keyframes.spec.ts index fb5cd14af..1cdfc9181 100644 --- a/packages/core/test/features/css-keyframes.spec.ts +++ b/packages/core/test/features/css-keyframes.spec.ts @@ -1,10 +1,17 @@ import { STSymbol, CSSKeyframes, STMixin } from '@stylable/core/dist/features'; -import { ignoreDeprecationWarn } from '@stylable/core/dist/helpers/deprecation'; -import { testStylableCore, shouldReportNoDiagnostics } from '@stylable/core-test-kit'; +import { + testStylableCore, + shouldReportNoDiagnostics, + diagnosticBankReportToStrings, +} from '@stylable/core-test-kit'; import chai, { expect } from 'chai'; import chaiSubset from 'chai-subset'; chai.use(chaiSubset); +const mixinDiagnostics = diagnosticBankReportToStrings(STMixin.diagnostics); +const keyframesDiagnostics = diagnosticBankReportToStrings(CSSKeyframes.diagnostics); +const stSymbolDiagnostics = diagnosticBankReportToStrings(STSymbol.diagnostics); + describe(`features/css-keyframes`, () => { it(`should process @keyframes`, () => { const { sheets } = testStylableCore(` @@ -53,12 +60,7 @@ describe(`features/css-keyframes`, () => { expect( CSSKeyframes.getKeyframesStatements(meta), `CSSKeyframes.getKeyframesStatements(meta)` - ).to.containSubset([meta.ast.nodes[1], meta.ast.nodes[3]]); - - // deprecation - ignoreDeprecationWarn(() => { - expect(meta.keyframes).to.eql(CSSKeyframes.getKeyframesStatements(meta)); - }); + ).to.containSubset([meta.sourceAst.nodes[1], meta.sourceAst.nodes[3]]); }); it(`should namespace "animation" and "animation-name" declarations`, () => { const { sheets } = testStylableCore(` @@ -119,21 +121,21 @@ describe(`features/css-keyframes`, () => { }); it('should report invalid cases', () => { const { sheets } = testStylableCore(` - /* @analyze-warn(empty name) ${CSSKeyframes.diagnostics.MISSING_KEYFRAMES_NAME()} */ + /* @analyze-error(empty name) ${keyframesDiagnostics.MISSING_KEYFRAMES_NAME()} */ @keyframes {} - /* @analyze-warn(empty global) ${CSSKeyframes.diagnostics.MISSING_KEYFRAMES_NAME_INSIDE_GLOBAL()} */ + /* @analyze-error(empty global) ${keyframesDiagnostics.MISSING_KEYFRAMES_NAME_INSIDE_GLOBAL()} */ @keyframes st-global() {} `); const { meta } = sheets['/entry.st.css']; - expect(meta.outputAst?.nodes[1]?.toString()).to.eql(`@keyframes {}`); + expect(meta.targetAst?.nodes[1]?.toString()).to.eql(`@keyframes {}`); }); it('should report reserved @keyframes names', () => { CSSKeyframes.reservedKeyFrames.map((reserved) => { testStylableCore(` - /* @analyze-error(${reserved}) word(${reserved}) ${CSSKeyframes.diagnostics.KEYFRAME_NAME_RESERVED( + /* @analyze-error(${reserved}) word(${reserved}) ${keyframesDiagnostics.KEYFRAME_NAME_RESERVED( reserved )} */ @keyframes ${reserved} {} @@ -170,7 +172,7 @@ describe(`features/css-keyframes`, () => { } .root { - /* @analyze-error ${CSSKeyframes.diagnostics.ILLEGAL_KEYFRAMES_NESTING()} */ + /* @analyze-error ${keyframesDiagnostics.ILLEGAL_KEYFRAMES_NESTING()} */ @keyframes not-valid {} } `); @@ -229,24 +231,24 @@ describe(`features/css-keyframes`, () => { describe(`multiple @keyframes`, () => { it(`should warn on redeclare keyframes in root`, () => { testStylableCore(` - /* @analyze-warn word(a) ${STSymbol.diagnostics.REDECLARE_SYMBOL(`a`)} */ + /* @analyze-warn word(a) ${stSymbolDiagnostics.REDECLARE_SYMBOL(`a`)} */ @keyframes a {} - /* @analyze-warn word(a) ${STSymbol.diagnostics.REDECLARE_SYMBOL(`a`)} */ + /* @analyze-warn word(a) ${stSymbolDiagnostics.REDECLARE_SYMBOL(`a`)} */ @keyframes a {} `); }); it(`should warn on redeclare keyframes in identical @media nesting`, () => { testStylableCore(` @media (max-width: 1px) { - /* @analyze-warn word(a) ${STSymbol.diagnostics.REDECLARE_SYMBOL(`a`)} */ + /* @analyze-warn word(a) ${stSymbolDiagnostics.REDECLARE_SYMBOL(`a`)} */ @keyframes a {} } @media (max-width: 1px) { - /* @analyze-warn word(a) ${STSymbol.diagnostics.REDECLARE_SYMBOL(`a`)} */ + /* @analyze-warn word(a) ${stSymbolDiagnostics.REDECLARE_SYMBOL(`a`)} */ @keyframes a {} - /* @analyze-warn word(a) ${STSymbol.diagnostics.REDECLARE_SYMBOL(`a`)} */ + /* @analyze-warn word(a) ${stSymbolDiagnostics.REDECLARE_SYMBOL(`a`)} */ @keyframes a {} } `); @@ -372,17 +374,17 @@ describe(`features/css-keyframes`, () => { /* @atrule entry__before - @analyze-warn(local before) word(before) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + @analyze-warn(local before) word(before) ${stSymbolDiagnostics.REDECLARE_SYMBOL( `before` )} */ @keyframes before {} /* - @analyze-warn(import before) word(before) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + @analyze-warn(import before) word(before) ${stSymbolDiagnostics.REDECLARE_SYMBOL( `before` )} - @analyze-warn(import after) word(after) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + @analyze-warn(import after) word(after) ${stSymbolDiagnostics.REDECLARE_SYMBOL( `after` )} */ @@ -390,7 +392,7 @@ describe(`features/css-keyframes`, () => { /* @atrule entry__after - @analyze-warn(local after) word(after) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + @analyze-warn(local after) word(after) ${stSymbolDiagnostics.REDECLARE_SYMBOL( `after` )} */ @@ -431,7 +433,7 @@ describe(`features/css-keyframes`, () => { const { sheets } = testStylableCore({ '/imported.st.css': ``, '/entry.st.css': ` - /* @transform-error word(unknown) ${CSSKeyframes.diagnostics.UNKNOWN_IMPORTED_KEYFRAMES( + /* @transform-error word(unknown) ${keyframesDiagnostics.UNKNOWN_IMPORTED_KEYFRAMES( `unknown`, `./imported.st.css` )} */ @@ -492,7 +494,7 @@ describe(`features/css-keyframes`, () => { `); expect( - sheets[`/entry.st.css`].meta.outputAst?.toString().match(/@keyframes/g)!.length, + sheets[`/entry.st.css`].meta.targetAst?.toString().match(/@keyframes/g)!.length, `only original @keyframes` ).to.eql(1); }); @@ -517,7 +519,7 @@ describe(`features/css-keyframes`, () => { }); expect( - sheets[`/entry.st.css`].meta.outputAst?.toString(), + sheets[`/entry.st.css`].meta.targetAst?.toString(), `@keyframes referenced & not copied` ).to.not.include(`@keyframes`); }); @@ -599,7 +601,7 @@ describe(`features/css-keyframes`, () => { @keyframes move { /* - @transform-warn ${STMixin.diagnostics.INVALID_MERGE_OF( + @transform-error ${mixinDiagnostics.INVALID_MERGE_OF( `0%:hover { color: red; }` diff --git a/packages/core/test/features/css-layer.spec.ts b/packages/core/test/features/css-layer.spec.ts index 250a44a4c..22611af7a 100644 --- a/packages/core/test/features/css-layer.spec.ts +++ b/packages/core/test/features/css-layer.spec.ts @@ -1,8 +1,14 @@ import { CSSLayer } from '@stylable/core/dist/features'; -import { testStylableCore, shouldReportNoDiagnostics } from '@stylable/core-test-kit'; +import { + testStylableCore, + shouldReportNoDiagnostics, + diagnosticBankReportToStrings, +} from '@stylable/core-test-kit'; import deindent from 'deindent'; import { expect } from 'chai'; +const cssLayerDiagnostics = diagnosticBankReportToStrings(CSSLayer.diagnostics); + describe('features/css-layer', () => { it('should analyze @layer', () => { const { sheets } = testStylableCore(` @@ -209,33 +215,34 @@ describe('features/css-layer', () => { it('should report invalid cases', () => { const { sheets } = testStylableCore(` /* - @analyze-warn(empty global) ${CSSLayer.diagnostics.MISSING_LAYER_NAME_INSIDE_GLOBAL()} + @analyze-warn(empty global) ${cssLayerDiagnostics.MISSING_LAYER_NAME_INSIDE_GLOBAL()} @atrule st-global() */ @layer st-global() {} - - /* @analyze-error(multi block) ${CSSLayer.diagnostics.LAYER_SORT_STATEMENT_WITH_STYLE()} */ + + /* @analyze-error(multi block) ${cssLayerDiagnostics.LAYER_SORT_STATEMENT_WITH_STYLE()} */ @layer one, two {} - - /* - @analyze-error(reserved wide keywords) word(initial) ${CSSLayer.diagnostics.RESERVED_KEYWORD( + + /* + @analyze-error(reserved wide keywords) word(initial) ${cssLayerDiagnostics.RESERVED_KEYWORD( 'initial' )} @atrule(reserved wide keywords) initial */ @layer initial {} - - /* - @analyze-error(not ident) ${CSSLayer.diagnostics.NOT_IDENT('func()')} + + /* + @analyze-error(not ident) ${cssLayerDiagnostics.NOT_IDENT('func()')} @atrule(not ident) func() */ @layer func() {} + `); // ToDo: check invalid ident "@layer 123 {}" / "@layer a.123.b;" const { meta } = sheets['/entry.st.css']; - expect(meta.outputAst?.nodes[1]?.toString()).to.eql(`@layer st-global() {}`); + expect(meta.targetAst?.nodes[1]?.toString()).to.eql(`@layer st-global() {}`); }); describe('st-import', () => { it('should resolve imported @layer', () => { @@ -328,7 +335,7 @@ describe('features/css-layer', () => { const { sheets } = testStylableCore({ '/imported.st.css': ``, '/entry.st.css': ` - /* @transform-error word(unknown) ${CSSLayer.diagnostics.UNKNOWN_IMPORTED_LAYER( + /* @transform-error word(unknown) ${cssLayerDiagnostics.UNKNOWN_IMPORTED_LAYER( `unknown`, `./imported.st.css` )} */ @@ -378,7 +385,7 @@ describe('features/css-layer', () => { @st-import [layer(L1)] from './imported.st.css'; /* - @analyze-error word(L1) ${CSSLayer.diagnostics.RECONFIGURE_IMPORTED('L1')} + @analyze-error word(L1) ${cssLayerDiagnostics.RECONFIGURE_IMPORTED('L1')} @atrule imported__L1 */ @layer st-global(L1) {} @@ -422,7 +429,7 @@ describe('features/css-layer', () => { shouldReportNoDiagnostics(meta); - expect(meta.outputAst?.toString()).to.eql( + expect(meta.targetAst?.toString()).to.eql( deindent(` @layer entry__x { .entry__before { id: before-in-layer; } diff --git a/packages/core/test/features/css-type.spec.ts b/packages/core/test/features/css-type.spec.ts index 9c7d990e7..b006b8420 100644 --- a/packages/core/test/features/css-type.spec.ts +++ b/packages/core/test/features/css-type.spec.ts @@ -1,8 +1,13 @@ import { STImport, CSSType, STSymbol } from '@stylable/core/dist/features'; -import { ignoreDeprecationWarn } from '@stylable/core/dist/helpers/deprecation'; -import { testStylableCore, shouldReportNoDiagnostics } from '@stylable/core-test-kit'; +import { + testStylableCore, + shouldReportNoDiagnostics, + diagnosticBankReportToStrings, +} from '@stylable/core-test-kit'; import { expect } from 'chai'; +const cssTypeDiagnostics = diagnosticBankReportToStrings(CSSType.diagnostics); + describe(`features/css-type`, () => { it(`should process element types`, () => { const { sheets } = testStylableCore(` @@ -35,21 +40,12 @@ describe(`features/css-type`, () => { CSSType.get(meta, `Btn`) ); expect(meta.getAllTypeElements(), `meta.getAllTypeElements`).to.eql(CSSType.getAll(meta)); - - // deprecation - expect( - ignoreDeprecationWarn(() => meta.elements), - `deprecated 'meta.elements'` - ).to.eql({ - Btn: CSSType.get(meta, `Btn`), - Gallery: CSSType.get(meta, `Gallery`), - }); }); it(`should report invalid cases`, () => { testStylableCore(` /* @rule(functional element type) div() - @analyze-error(functional element type) ${CSSType.diagnostics.INVALID_FUNCTIONAL_SELECTOR( + @analyze-error(functional element type) ${cssTypeDiagnostics.INVALID_FUNCTIONAL_SELECTOR( `div`, `type` )} @@ -63,7 +59,7 @@ describe(`features/css-type`, () => { anywhere in the selector: "div .local span" */ const { sheets } = testStylableCore(` - /* @analyze-warn word(button) ${CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR( + /* @analyze-warn word(button) ${cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR( `button` )} */ button {} @@ -180,7 +176,7 @@ describe(`features/css-type`, () => { '/entry.st.css': ` @st-import [importedPart] from "./classes.st.css"; - /* @analyze-warn word(importedPart) ${CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR( + /* @analyze-warn word(importedPart) ${cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR( `importedPart` )} */ importedPart {} diff --git a/packages/core/test/features/st-custom-selector.spec.ts b/packages/core/test/features/st-custom-selector.spec.ts index d380ff61c..c93db8320 100644 --- a/packages/core/test/features/st-custom-selector.spec.ts +++ b/packages/core/test/features/st-custom-selector.spec.ts @@ -1,10 +1,17 @@ import chaiSubset from 'chai-subset'; -import { CSSType } from '@stylable/core/dist/features'; -import { testStylableCore, shouldReportNoDiagnostics } from '@stylable/core-test-kit'; +import { STCustomSelector, CSSType } from '@stylable/core/dist/features'; +import { + testStylableCore, + shouldReportNoDiagnostics, + diagnosticBankReportToStrings, +} from '@stylable/core-test-kit'; import chai, { expect } from 'chai'; chai.use(chaiSubset); +const customSelectorDiagnostics = diagnosticBankReportToStrings(STCustomSelector.diagnostics); +const cssTypeDiagnostics = diagnosticBankReportToStrings(CSSType.diagnostics); + describe('features/st-custom-selector', () => { // ToDo: move and add tests when extracting feature // ToDo: migrate to @st-custom-selector @@ -36,9 +43,25 @@ describe('features/st-custom-selector', () => { shouldReportNoDiagnostics(meta); }); + it('should handle unknown custom selector', () => { + testStylableCore(` + /* @analyze-error(in custom) word(:--unknown) ${customSelectorDiagnostics.UNKNOWN_CUSTOM_SELECTOR( + ':--unknown' + )} */ + @custom-selector :--x .before:--unknown.after; + + /* + @transform-error(in selector) word(:--unknown) ${customSelectorDiagnostics.UNKNOWN_CUSTOM_SELECTOR( + ':--unknown' + )} + @rule .entry__before:--unknown.entry__after {} + */ + .before:--unknown.after {} + `); + }); it('should report selector on atrule', () => { testStylableCore(` - /* @analyze-error ${CSSType.diagnostics.INVALID_FUNCTIONAL_SELECTOR('div', 'type')} */ + /* @analyze-error ${cssTypeDiagnostics.INVALID_FUNCTIONAL_SELECTOR('div', 'type')} */ @custom-selector :--functional-div div(); `); }); @@ -47,7 +70,7 @@ describe('features/st-custom-selector', () => { @custom-selector :--unscoped div; @custom-selector :--scoped .root div; - /* @analyze-warn ${CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR('span')} */ + /* @analyze-warn ${cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR('span')} */ :--unscoped span {} :--scoped ul {} @@ -60,4 +83,17 @@ describe('features/st-custom-selector', () => { 'only a single unscoped diagnostic for span' ).to.eql(1); }); + describe('css-pseudo-element', () => { + // + it.skip('should handle circular reference', () => { + // ToDo: refactor handleCustomSelector transformer flow to handle circularity + testStylableCore(` + @custom-selector :--x ::y; + @custom-selector :--y ::x; + + /* @rule :--y */ + :--y {} + `); + }); + }); }); diff --git a/packages/core/test/features/st-global.spec.ts b/packages/core/test/features/st-global.spec.ts index ee590e853..7a2015414 100644 --- a/packages/core/test/features/st-global.spec.ts +++ b/packages/core/test/features/st-global.spec.ts @@ -1,7 +1,13 @@ import { STGlobal } from '@stylable/core/dist/features'; -import { testStylableCore, shouldReportNoDiagnostics } from '@stylable/core-test-kit'; +import { + testStylableCore, + shouldReportNoDiagnostics, + diagnosticBankReportToStrings, +} from '@stylable/core-test-kit'; import { expect } from 'chai'; +const stGlobalDiagnostics = diagnosticBankReportToStrings(STGlobal.diagnostics); + describe(`features/st-global`, () => { it(`should remove :global() and keep inner selector untransformed`, () => { const { sheets } = testStylableCore(` @@ -40,7 +46,7 @@ describe(`features/st-global`, () => { testStylableCore(` /* @rule(multi) :global(.a, .b) - @analyze-error(multi) word(.a, .b) ${STGlobal.diagnostics.UNSUPPORTED_MULTI_SELECTOR_IN_GLOBAL()} + @analyze-error(multi) word(.a, .b) ${stGlobalDiagnostics.UNSUPPORTED_MULTI_SELECTOR_IN_GLOBAL()} */ :global(.a, .b) {} `); diff --git a/packages/core/test/features/st-import.spec.ts b/packages/core/test/features/st-import.spec.ts index d650351ba..2de03c0a9 100644 --- a/packages/core/test/features/st-import.spec.ts +++ b/packages/core/test/features/st-import.spec.ts @@ -1,10 +1,16 @@ import { STImport, STSymbol } from '@stylable/core/dist/features'; -import { ignoreDeprecationWarn } from '@stylable/core/dist/helpers/deprecation'; -import { testStylableCore, shouldReportNoDiagnostics } from '@stylable/core-test-kit'; +import { + testStylableCore, + shouldReportNoDiagnostics, + diagnosticBankReportToStrings, +} from '@stylable/core-test-kit'; import chai, { expect } from 'chai'; import chaiSubset from 'chai-subset'; chai.use(chaiSubset); +const stImportDiagnostics = diagnosticBankReportToStrings(STImport.diagnostics); +const stSymbolDiagnostics = diagnosticBankReportToStrings(STSymbol.diagnostics); + describe(`features/st-import`, () => { it(`should collect import statements`, () => { const { sheets } = testStylableCore(` @@ -59,11 +65,6 @@ describe(`features/st-import`, () => { expect(meta.getImportStatements(), `meta.getImportStatements()`).to.eql( STImport.getImportStatements(meta) ); - // deprecation - expect( - ignoreDeprecationWarn(() => meta.imports), - `deprecated 'meta.imports'` - ).to.eql(meta.getImportStatements()); }); it(`should process imported symbols`, () => { const { sheets } = testStylableCore({ @@ -102,7 +103,7 @@ describe(`features/st-import`, () => { .x { /* @transform-remove - @analyze-warn ${STImport.diagnostics.NO_ST_IMPORT_IN_NESTED_SCOPE()} + @analyze-error ${stImportDiagnostics.NO_ST_IMPORT_IN_NESTED_SCOPE()} */ @st-import D, [n] from "./some/external/path"; } @@ -144,10 +145,10 @@ describe(`features/st-import`, () => { 'other.st.css': ``, 'entry.st.css': ` /* - @analyze-error word(unknown) ${STImport.diagnostics.UNKNOWN_TYPED_IMPORT( + @analyze-error word(unknown) ${stImportDiagnostics.UNKNOWN_TYPED_IMPORT( 'unknown' )} - @analyze-error word(classes) ${STImport.diagnostics.UNKNOWN_TYPED_IMPORT( + @analyze-error word(classes) ${stImportDiagnostics.UNKNOWN_TYPED_IMPORT( 'classes' )} */ @@ -157,7 +158,7 @@ describe(`features/st-import`, () => { }); it(`should warn on lowercase default import from css file`, () => { const { sheets } = testStylableCore(` - /* @analyze-warn word(sheetError) ${STImport.diagnostics.DEFAULT_IMPORT_IS_LOWER_CASE()} */ + /* @analyze-warn word(sheetError) ${stImportDiagnostics.DEFAULT_IMPORT_IS_LOWER_CASE()} */ @st-import sheetError from "./a.st.css"; @st-import SheetStartWithCapital from "./b.st.css"; @@ -168,27 +169,27 @@ describe(`features/st-import`, () => { }); it(`should handle invalid cases`, () => { testStylableCore(` - /* @analyze-error(empty from) ${STImport.diagnostics.ST_IMPORT_EMPTY_FROM()} */ + /* @analyze-error(empty from) ${stImportDiagnostics.ST_IMPORT_EMPTY_FROM()} */ @st-import A from ""; - /* @analyze-error(spaces only from) ${STImport.diagnostics.ST_IMPORT_EMPTY_FROM()} */ + /* @analyze-error(spaces only from) ${stImportDiagnostics.ST_IMPORT_EMPTY_FROM()} */ @st-import A from " "; - /* @analyze-error(* import) ${STImport.diagnostics.ST_IMPORT_STAR()} */ + /* @analyze-error(* import) ${stImportDiagnostics.ST_IMPORT_STAR()} */ @st-import * as X from "./some/path"; - /* @analyze-error(* import) ${STImport.diagnostics.INVALID_ST_IMPORT_FORMAT([ + /* @analyze-error(* import) ${stImportDiagnostics.INVALID_ST_IMPORT_FORMAT([ `invalid missing source`, ])} */ @st-import %# from (""); - /* @analyze-error(missing from) ${STImport.diagnostics.INVALID_ST_IMPORT_FORMAT([ + /* @analyze-error(missing from) ${stImportDiagnostics.INVALID_ST_IMPORT_FORMAT([ `invalid missing from`, `invalid missing source`, ])} */ @st-import f rom "x"; - /* @analyze-warn(invalid mapped custom prop) ${STImport.diagnostics.INVALID_CUSTOM_PROPERTY_AS_VALUE( + /* @analyze-error(invalid mapped custom prop) ${stImportDiagnostics.INVALID_CUSTOM_PROPERTY_AS_VALUE( `--x`, `z` )} */ @@ -197,12 +198,12 @@ describe(`features/st-import`, () => { }); it(`should error on unresolved file`, () => { testStylableCore(` - /* @transform-warn(relative) word(./missing.st.css) ${STImport.diagnostics.UNKNOWN_IMPORTED_FILE( + /* @transform-error(relative) word(./missing.st.css) ${stImportDiagnostics.UNKNOWN_IMPORTED_FILE( `./missing.st.css` )} */ @st-import "./missing.st.css"; - /* @transform-warn(3rd party) word(missing-package/index.st.css) ${STImport.diagnostics.UNKNOWN_IMPORTED_FILE( + /* @transform-error(3rd party) word(missing-package/index.st.css) ${stImportDiagnostics.UNKNOWN_IMPORTED_FILE( `missing-package/index.st.css` )} */ @st-import "missing-package/index.st.css"; @@ -212,13 +213,13 @@ describe(`features/st-import`, () => { testStylableCore({ '/empty.st.css': ``, '/entry.st.css': ` - /* @transform-warn(named) word(unknown) ${STImport.diagnostics.UNKNOWN_IMPORTED_SYMBOL( + /* @transform-error(named) word(unknown) ${stImportDiagnostics.UNKNOWN_IMPORTED_SYMBOL( `unknown`, `./empty.st.css` )} */ @st-import [unknown] "./empty.st.css"; - /* @transform-warn(mapped) word(unknown) ${STImport.diagnostics.UNKNOWN_IMPORTED_SYMBOL( + /* @transform-error(mapped) word(unknown) ${stImportDiagnostics.UNKNOWN_IMPORTED_SYMBOL( `unknown`, `./empty.st.css` )} */ @@ -230,10 +231,10 @@ describe(`features/st-import`, () => { it(`should warn on redeclare between multiple import statements`, () => { testStylableCore({ '/entry.st.css': ` - /* @analyze-warn ${STSymbol.diagnostics.REDECLARE_SYMBOL(`Name`)} */ + /* @analyze-warn ${stSymbolDiagnostics.REDECLARE_SYMBOL(`Name`)} */ @st-import Name from "./file.st.css"; - /* @analyze-warn ${STSymbol.diagnostics.REDECLARE_SYMBOL(`Name`)} */ + /* @analyze-warn ${stSymbolDiagnostics.REDECLARE_SYMBOL(`Name`)} */ @st-import Name from "./file.st.css"; `, }); @@ -241,7 +242,7 @@ describe(`features/st-import`, () => { it(`should warn on redeclare within a single import symbol`, () => { const { sheets } = testStylableCore({ '/entry.st.css': ` - /* @analyze-warn ${STSymbol.diagnostics.REDECLARE_SYMBOL(`Name`)} */ + /* @analyze-warn ${stSymbolDiagnostics.REDECLARE_SYMBOL(`Name`)} */ @st-import Name, [Name] from "./file.st.css" `, }); @@ -249,7 +250,7 @@ describe(`features/st-import`, () => { const { meta } = sheets['/entry.st.css']; const reports = meta.diagnostics.reports.filter( - ({ message }) => message === STSymbol.diagnostics.REDECLARE_SYMBOL(`Name`) + ({ message }) => message === stSymbolDiagnostics.REDECLARE_SYMBOL(`Name`) ); expect(reports.length, `for both default and name`).to.eql(2); }); @@ -323,11 +324,6 @@ describe(`features/st-import`, () => { expect(meta.getImportStatements(), `meta.getImportStatements()`).to.eql( STImport.getImportStatements(meta) ); - // deprecation - expect( - ignoreDeprecationWarn(() => meta.imports), - `deprecated 'meta.imports'` - ).to.eql(meta.getImportStatements()); }); it(`should process imported symbols`, () => { const { sheets } = testStylableCore({ @@ -369,7 +365,7 @@ describe(`features/st-import`, () => { .x { /* @transform-remove - @analyze-warn ${STImport.diagnostics.NO_PSEUDO_IMPORT_IN_NESTED_SCOPE()} + @analyze-error ${stImportDiagnostics.NO_PSEUDO_IMPORT_IN_NESTED_SCOPE()} */ :import { -st-from: "./some/external/path.st.css"; @@ -423,10 +419,10 @@ describe(`features/st-import`, () => { 'other.st.css': ``, 'entry.st.css': ` /* - @analyze-error word(unknown) ${STImport.diagnostics.UNKNOWN_TYPED_IMPORT( + @analyze-error word(unknown) ${stImportDiagnostics.UNKNOWN_TYPED_IMPORT( 'unknown' )} - @analyze-error word(classes) ${STImport.diagnostics.UNKNOWN_TYPED_IMPORT( + @analyze-error word(classes) ${stImportDiagnostics.UNKNOWN_TYPED_IMPORT( 'classes' )} */ @@ -441,7 +437,7 @@ describe(`features/st-import`, () => { const { sheets } = testStylableCore(` :import{ -st-from:"./a.st.css"; - /* @analyze-warn word(sheetError) ${STImport.diagnostics.DEFAULT_IMPORT_IS_LOWER_CASE()} */ + /* @analyze-warn word(sheetError) ${stImportDiagnostics.DEFAULT_IMPORT_IS_LOWER_CASE()} */ -st-default: sheetError; } @@ -462,18 +458,18 @@ describe(`features/st-import`, () => { // ToDo: add diagnostic for multiple -st-named testStylableCore(` :import{ - /* @analyze-error(empty from) ${STImport.diagnostics.EMPTY_IMPORT_FROM()} */ + /* @analyze-error(empty from) ${stImportDiagnostics.EMPTY_IMPORT_FROM()} */ -st-from: ""; -st-default: Comp; } :import{ - /* @analyze-error(spaces only from) ${STImport.diagnostics.EMPTY_IMPORT_FROM()} */ + /* @analyze-error(spaces only from) ${stImportDiagnostics.EMPTY_IMPORT_FROM()} */ -st-from: " "; -st-default: Comp; } - /* @analyze-warn(invalid mapped custom prop) ${STImport.diagnostics.INVALID_CUSTOM_PROPERTY_AS_VALUE( + /* @analyze-error(invalid mapped custom prop) ${stImportDiagnostics.INVALID_CUSTOM_PROPERTY_AS_VALUE( `--x`, `z` )} */ @@ -482,12 +478,12 @@ describe(`features/st-import`, () => { -st-named: --x as z; } - /* @analyze-error(missing from) ${STImport.diagnostics.FROM_PROP_MISSING_IN_IMPORT()} */ + /* @analyze-error(missing from) ${stImportDiagnostics.FROM_PROP_MISSING_IN_IMPORT()} */ :import{ -st-default: Comp; } - /* @analyze-warn(multiple from) ${STImport.diagnostics.MULTIPLE_FROM_IN_IMPORT()} */ + /* @analyze-warn(multiple from) ${stImportDiagnostics.MULTIPLE_FROM_IN_IMPORT()} */ :import{ -st-from: "a"; -st-from: "b"; @@ -497,7 +493,7 @@ describe(`features/st-import`, () => { :import{ -st-from:"./imported.st.css"; -st-default:Comp; - /* @analyze-warn(unknown declaration) word(color) ${STImport.diagnostics.ILLEGAL_PROP_IN_IMPORT( + /* @analyze-warn(unknown declaration) word(color) ${stImportDiagnostics.ILLEGAL_PROP_IN_IMPORT( `color` )} */ color:red; @@ -507,14 +503,14 @@ describe(`features/st-import`, () => { it(`should error on unresolved file`, () => { testStylableCore(` :import{ - /* @transform-warn(relative) word(./missing.st.css) ${STImport.diagnostics.UNKNOWN_IMPORTED_FILE( + /* @transform-error(relative) word(./missing.st.css) ${stImportDiagnostics.UNKNOWN_IMPORTED_FILE( `./missing.st.css` )} */ -st-from: "./missing.st.css"; } :import{ - /* @transform-warn(3rd party) word(missing-package/index.st.css) ${STImport.diagnostics.UNKNOWN_IMPORTED_FILE( + /* @transform-error(3rd party) word(missing-package/index.st.css) ${stImportDiagnostics.UNKNOWN_IMPORTED_FILE( `missing-package/index.st.css` )} */ -st-from: "missing-package/index.st.css"; @@ -527,7 +523,7 @@ describe(`features/st-import`, () => { '/entry.st.css': ` :import{ -st-from: "./empty.st.css"; - /* @transform-warn(named) word(unknown) ${STImport.diagnostics.UNKNOWN_IMPORTED_SYMBOL( + /* @transform-error(named) word(unknown) ${stImportDiagnostics.UNKNOWN_IMPORTED_SYMBOL( `unknown`, `./empty.st.css` )} */ @@ -536,7 +532,7 @@ describe(`features/st-import`, () => { :import{ -st-from: "./empty.st.css"; - /* @transform-warn(mapped) word(unknown) ${STImport.diagnostics.UNKNOWN_IMPORTED_SYMBOL( + /* @transform-error(mapped) word(unknown) ${stImportDiagnostics.UNKNOWN_IMPORTED_SYMBOL( `unknown`, `./empty.st.css` )} */ @@ -548,7 +544,7 @@ describe(`features/st-import`, () => { it(`should not allow in complex selector`, () => { testStylableCore({ '/entry.st.css': ` - /* @analyze-warn ${STImport.diagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR( + /* @analyze-error ${stImportDiagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR( `:import` )} */ .gaga:import { @@ -562,13 +558,13 @@ describe(`features/st-import`, () => { it(`should warn on redeclare between multiple import statements`, () => { testStylableCore({ '/entry.st.css': ` - /* @analyze-warn ${STSymbol.diagnostics.REDECLARE_SYMBOL(`Name`)} */ + /* @analyze-warn ${stSymbolDiagnostics.REDECLARE_SYMBOL(`Name`)} */ :import { -st-from: './file.st.css'; -st-default: Name; } - /* @analyze-warn ${STSymbol.diagnostics.REDECLARE_SYMBOL(`Name`)} */ + /* @analyze-warn ${stSymbolDiagnostics.REDECLARE_SYMBOL(`Name`)} */ :import { -st-from: './file.st.css'; -st-default: Name; @@ -579,7 +575,7 @@ describe(`features/st-import`, () => { it(`should warn on redeclare within a single import symbol`, () => { const { sheets } = testStylableCore({ '/entry.st.css': ` - /* @analyze-warn ${STSymbol.diagnostics.REDECLARE_SYMBOL(`Name`)} */ + /* @analyze-warn ${stSymbolDiagnostics.REDECLARE_SYMBOL(`Name`)} */ :import { -st-from: './file.st.css'; -st-default: Name; @@ -591,10 +587,85 @@ describe(`features/st-import`, () => { const { meta } = sheets['/entry.st.css']; const reports = meta.diagnostics.reports.filter( - ({ message }) => message === STSymbol.diagnostics.REDECLARE_SYMBOL(`Name`) + ({ message }) => message === stSymbolDiagnostics.REDECLARE_SYMBOL(`Name`) ); expect(reports.length, `for both default and name`).to.eql(2); }); }); }); + describe('stylable API', () => { + it('should analyze imports', () => { + const { stylable, sheets } = testStylableCore({ + '/dir/entry.st.css': ` + @st-import "./no/imported/symbols"; + + @st-import "../parent-dir"; + + @st-import "/absolute/path"; + + @st-import a from "./default/import"; + + @st-import [b, c as x] from "./named/import"; + + @st-import d, [e] from "./default&named/import"; + + @st-import [f, keyframes(key1, key2 as localKey)] from "./keyframes"; + `, + }); + + const { meta } = sheets['/dir/entry.st.css']; + + const analyzedImports = stylable.stModule.analyze(meta); + + expect(analyzedImports).to.eql([ + { + default: '', + named: {}, + from: './no/imported/symbols', + typed: { keyframes: {} }, + }, + { + default: '', + named: {}, + from: '../parent-dir', + typed: { keyframes: {} }, + }, + { + default: '', + named: {}, + from: '/absolute/path', + typed: { keyframes: {} }, + }, + { + default: 'a', + named: {}, + from: './default/import', + typed: { keyframes: {} }, + }, + { + default: '', + named: { b: 'b', x: 'c' }, + from: './named/import', + typed: { keyframes: {} }, + }, + { + default: 'd', + named: { e: 'e' }, + from: './default&named/import', + typed: { keyframes: {} }, + }, + { + default: '', + named: { f: 'f' }, + from: './keyframes', + typed: { + keyframes: { + key1: 'key1', + localKey: 'key2', + }, + }, + }, + ]); + }); + }); }); diff --git a/packages/core/test/features/st-mixin.spec.ts b/packages/core/test/features/st-mixin.spec.ts index af9162729..4cd1e3381 100644 --- a/packages/core/test/features/st-mixin.spec.ts +++ b/packages/core/test/features/st-mixin.spec.ts @@ -1,15 +1,17 @@ import chaiSubset from 'chai-subset'; -import type { SRule } from '@stylable/core'; +import { Diagnostics } from '@stylable/core'; import { STMixin } from '@stylable/core/dist/features'; -import { ignoreDeprecationWarn } from '@stylable/core/dist/helpers/deprecation'; import { testStylableCore, shouldReportNoDiagnostics, matchRuleAndDeclaration, + diagnosticBankReportToStrings, } from '@stylable/core-test-kit'; import chai, { expect } from 'chai'; import type * as postcss from 'postcss'; +const mixinDiagnostics = diagnosticBankReportToStrings(STMixin.diagnostics); + chai.use(chaiSubset); describe(`features/st-mixin`, () => { it(`should append mixin declarations`, () => { @@ -134,7 +136,7 @@ describe(`features/st-mixin`, () => { it(`should handle circular mixins`, () => { testStylableCore(` /* - @transform-warn(a) ${STMixin.diagnostics.CIRCULAR_MIXIN([ + @transform-error(a) ${mixinDiagnostics.CIRCULAR_MIXIN([ `b from /entry.st.css`, `a from /entry.st.css`, ])} @@ -149,7 +151,7 @@ describe(`features/st-mixin`, () => { } /* - @transform-warn(a) ${STMixin.diagnostics.CIRCULAR_MIXIN([ + @transform-error(a) ${mixinDiagnostics.CIRCULAR_MIXIN([ `a from /entry.st.css`, `b from /entry.st.css`, ])} @@ -213,6 +215,26 @@ describe(`features/st-mixin`, () => { shouldReportNoDiagnostics(meta); }); + it(`should report unknown args`, () => { + testStylableCore(` + :vars { + a: red; + point_to_a: value(a); + } + .mix { + color: value(point_to_a); + } + + /* @rule .entry__root { color: green } */ + .root { + /* @transform-warn word(unknown) ${mixinDiagnostics.UNKNOWN_ARG('unknown')} */ + -st-mixin: mix( + a green, + unknown red + ); + } + `); + }); it(`should handle invalid cases`, () => { testStylableCore(` .mixA { @@ -224,16 +246,35 @@ describe(`features/st-mixin`, () => { /* @rule .entry__root { -st-mixin: "mixA" } */ .root { - /* @analyze-error ${STMixin.diagnostics.VALUE_CANNOT_BE_STRING()} */ + /* @transform-error ${mixinDiagnostics.VALUE_CANNOT_BE_STRING()} */ -st-mixin: "mixA"; } /* @rule .entry__root { color: green } */ .root { -st-mixin: mixA; - /* @analyze-warn ${STMixin.diagnostics.OVERRIDE_MIXIN(`-st-mixin`)} */ + /* @transform-warn ${mixinDiagnostics.OVERRIDE_MIXIN(`-st-mixin`)} */ -st-mixin: mixB; } + + :vars { + colorX: red; + } + /* @rule .entry__root { } */ + .root { + /* @transform-error ${mixinDiagnostics.UNSUPPORTED_MIXIN_SYMBOL(`colorX`, 'var')} */ + -st-mixin: colorX; + } + + @property --customPropX; + /* @rule .entry__root { } */ + .root { + /* @transform-error ${mixinDiagnostics.UNSUPPORTED_MIXIN_SYMBOL( + `--customPropX`, + 'cssVar' + )} */ + -st-mixin: --customPropX; + } `); }); it(`should not mix mixin that is removed before transform`, () => { @@ -257,7 +298,7 @@ describe(`features/st-mixin`, () => { stylableConfig: { onProcess(meta) { // remove -st-mixin origin before apply mixin. - const mixToClass = meta.ast.nodes[2] as postcss.Rule; + const mixToClass = meta.sourceAst.nodes[2] as postcss.Rule; const stMixinDecl = mixToClass.nodes[1]; stMixinDecl.remove(); return meta; @@ -266,148 +307,6 @@ describe(`features/st-mixin`, () => { } ); }); - describe(`SRule (deprecated)`, () => { - // ToDo: remove in v5 when SRule is removed - it(`should collect mixins on rules`, () => { - testStylableCore( - ` - .x { - -st-mixin: my-mixin - } - .my-mixin {} - `, - { - stylableConfig: { - onProcess(meta) { - const mixinRule = meta.ast.nodes[0] as SRule; - expect( - ignoreDeprecationWarn(() => mixinRule.mixins!)[0].mixin.type - ).to.eql('my-mixin'); - - return meta; - }, - }, - } - ); - }); - it(`should use last mixin deceleration`, () => { - testStylableCore( - ` - .x { - -st-mixin: my-mixin1; - -st-mixin: my-mixin2; - } - .my-mixin1 {} - .my-mixin2 {} - `, - { - stylableConfig: { - onProcess(meta) { - const mixinRule = meta.ast.nodes[0] as SRule; - expect( - ignoreDeprecationWarn(() => mixinRule.mixins!)[0].mixin.type - ).to.eql('my-mixin2'); - - return meta; - }, - }, - } - ); - }); - it(`should use last mixin deceleration for -st-partial-mixin`, () => { - testStylableCore( - ` - .x { - -st-partial-mixin: my-mixin1; - -st-partial-mixin: my-mixin2; - } - .my-mixin1 {} - .my-mixin2 {} - `, - { - stylableConfig: { - onProcess(meta) { - const mixinRule = meta.ast.nodes[0] as SRule; - expect( - ignoreDeprecationWarn(() => mixinRule.mixins!)[0].mixin.type - ).to.eql('my-mixin2'); - - return meta; - }, - }, - } - ); - }); - it(`should use mixin deceleration in order for mixed -st-mixin and -st-partial-mixin`, () => { - testStylableCore( - ` - .x { - -st-mixin: my-mixin1; - -st-partial-mixin: my-mixin2; - } - .y { - -st-partial-mixin: my-mixin2; - -st-mixin: my-mixin1; - } - .my-mixin1 {} - .my-mixin2 {} - `, - { - stylableConfig: { - onProcess(meta) { - const mixinRule1 = meta.ast.nodes[0] as SRule; - const mixinRule2 = meta.ast.nodes[1] as SRule; - expect( - ignoreDeprecationWarn(() => mixinRule1.mixins!)[0].mixin.type - ).to.eql('my-mixin1'); - expect( - ignoreDeprecationWarn(() => mixinRule1.mixins!)[1].mixin.type - ).to.eql('my-mixin2'); - expect( - ignoreDeprecationWarn(() => mixinRule2.mixins!)[0].mixin.type - ).to.eql('my-mixin2'); - expect( - ignoreDeprecationWarn(() => mixinRule2.mixins!)[1].mixin.type - ).to.eql('my-mixin1'); - - return meta; - }, - }, - } - ); - }); - it(`should use mixin last deceleration in order for mixed -st-mixin and -st-partial-mixin with duplicates`, () => { - testStylableCore( - ` - .x { - -st-mixin: my-mixin1; - -st-partial-mixin: my-mixin2; - -st-mixin: my-mixin3; - -st-partial-mixin: my-mixin4; - } - .my-mixin1 {} - .my-mixin2 {} - .my-mixin3 {} - .my-mixin4 {} - `, - { - stylableConfig: { - onProcess(meta) { - const mixinRule = meta.ast.nodes[0] as SRule; - expect( - ignoreDeprecationWarn(() => mixinRule.mixins!)[0].mixin.type - ).to.eql('my-mixin3'); - expect( - ignoreDeprecationWarn(() => mixinRule.mixins!)[1].mixin.type - ).to.eql('my-mixin4'); - - return meta; - }, - }, - } - ); - }); - }); describe(`st-import`, () => { it(`should mix imported class`, () => { const { sheets } = testStylableCore({ @@ -528,13 +427,44 @@ describe(`features/st-mixin`, () => { shouldReportNoDiagnostics(meta); }); + it(`should report unknown args (from another sheet)`, () => { + const { sheets } = testStylableCore({ + '/vars.st.css': ` + :vars { + a: red; + point_to_a: value(a); + } + `, + '/entry.st.css': ` + @st-import [point_to_a] from './vars.st.css'; + .mix { + color: value(point_to_a); + } + + /* @rule .entry__root { color: green } */ + .root { + /* @transform-warn word(unknown) ${mixinDiagnostics.UNKNOWN_ARG( + 'unknown' + )} */ + -st-mixin: mix( + a green, + unknown red + ); + } + `, + }); + + const { meta } = sheets['/entry.st.css']; + + expect(meta.transformDiagnostics?.reports.length).to.eql(1); + }); it(`should handle circular mixins from multiple stylesheets`, () => { // ToDo: check why circular_mixin is not reported testStylableCore({ '/sheet1.st.css': ` @st-import [b] from './sheet2.st.css'; /* - @xtransform-warn(a) ${STMixin.diagnostics.CIRCULAR_MIXIN([ + @xtransform-warn(a) ${mixinDiagnostics.CIRCULAR_MIXIN([ `b from /sheet2.st.css`, `a from /sheet1.st.css`, ])} @@ -551,7 +481,7 @@ describe(`features/st-mixin`, () => { '/sheet2.st.css': ` @st-import [a] from './sheet1.st.css'; /* - @xtransform-warn(a) ${STMixin.diagnostics.CIRCULAR_MIXIN([ + @xtransform-warn(a) ${mixinDiagnostics.CIRCULAR_MIXIN([ `a from /sheet1.st.css`, `b from /sheet2.st.css`, ])} @@ -572,14 +502,12 @@ describe(`features/st-mixin`, () => { @st-import [unresolved] from './mixin.st.css'; .a { - /* @analyze-warn ${STMixin.diagnostics.UNKNOWN_MIXIN(`unknown`)} */ + /* @transform-error ${mixinDiagnostics.UNKNOWN_MIXIN(`unknown`)} */ -st-mixin: unknown; } .a { - /* @transform-error ${STMixin.diagnostics.UNKNOWN_MIXIN_SYMBOL( - `unresolved` - )} */ + /* @transform-error ${mixinDiagnostics.UNKNOWN_MIXIN(`unresolved`)} */ -st-mixin: unresolved; } `, @@ -616,7 +544,7 @@ describe(`features/st-mixin`, () => { it(`should report on circular mixin when mixed on local class`, () => { testStylableCore(` /* - @transform-warn ${STMixin.diagnostics.CIRCULAR_MIXIN([`root from /entry.st.css`])} + @transform-error ${mixinDiagnostics.CIRCULAR_MIXIN([`root from /entry.st.css`])} @rule(self)[0] .entry__a {} @rule(self appended)[1] .entry__a .entry__a {} @rule(other appended)[2] .entry__a .entry__b {} @@ -786,11 +714,10 @@ describe(`features/st-mixin`, () => { .mix-deep { propA: value(v1); propB: value(v2); - propC: value(v3); } .mix { -st-partial-mixin: mix-deep(v2 value(v1)); - propX: value(v3); + propX: value(v2); } /* @@ -803,22 +730,13 @@ describe(`features/st-mixin`, () => { .SEP {} /* - @rule(v2) .entry__a { } + @rule(v2) .entry__a { propX: white } @rule(v2-end)[1] .entry__SEP */ .a { -st-partial-mixin: mix(v2 white); } .SEP {} - - /* - @rule(v3) .entry__a { propX: white } - @rule(v3-end)[1] .entry__SEP - */ - .a { - -st-partial-mixin: mix(v3 white); - } - .SEP {} `); const { meta } = sheets['/entry.st.css']; @@ -869,7 +787,7 @@ describe(`features/st-mixin`, () => { /* @rule(v1) .entry__a { } */ .a { - /* @analyze-warn word(mix-color) ${STMixin.diagnostics.PARTIAL_MIXIN_MISSING_ARGUMENTS( + /* @transform-error word(mix-color) ${mixinDiagnostics.PARTIAL_MIXIN_MISSING_ARGUMENTS( `mix-color` )} */ -st-partial-mixin: mix-color(); @@ -1274,12 +1192,12 @@ describe(`features/st-mixin`, () => { '/entry.st.css': ` @st-import [notAFunction, throw] from './mixins.js'; - /* @transform-error(not a function) word(notAFunction) ${STMixin.diagnostics.JS_MIXIN_NOT_A_FUNC()} */ .a { + /* @transform-error(not a function) word(notAFunction) ${mixinDiagnostics.JS_MIXIN_NOT_A_FUNC()} */ -st-mixin: notAFunction; } - /* @transform-error(mix throw) word(throw) ${STMixin.diagnostics.FAILED_TO_APPLY_MIXIN( + /* @transform-error(mix throw) word(throw) ${mixinDiagnostics.FAILED_TO_APPLY_MIXIN( `bug in js mix` )} */ .a { @@ -1483,7 +1401,7 @@ describe(`features/st-mixin`, () => { /* @rule .entry__root {val: local} */ .root { - /* @transform-warn ${STMixin.diagnostics.INVALID_NAMED_PARAMS()} */ + /* @transform-error ${mixinDiagnostics.INVALID_NAMED_PARAMS()} */ -st-mixin: mix(varNameWithNoValue); } `); @@ -1653,6 +1571,64 @@ describe(`features/st-mixin`, () => { shouldReportNoDiagnostics(meta); }); }); + describe(`st-custom-selector`, () => { + it(`should mix class through a custom selector`, () => { + const { sheets } = testStylableCore(` + @custom-selector :--x .x, .not-x; + + :--x { + color: green; + } + + /* @rule(inline) .entry__a { color: green } */ + .a { + -st-mixin: x; + } + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + it(`should mix class through a custom selector (deep)`, () => { + const { sheets } = testStylableCore(` + @custom-selector :--a .a; + @custom-selector :--b .b:--a; + + .mix:--b { + color: green; + } + + /* @rule(deep)[1] .entry__c.entry__b.entry__a { color: green } */ + .c { + -st-mixin: mix; + } + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + it.skip(`should attempt to correct compound order`, () => { + // ToDo: fix compound selector ordering + const { sheets } = testStylableCore(` + @custom-selector :--div div; + + .mix:--div { + color: green; + } + + /* @rule(fix composed)[1] div.entry__a { color: green } */ + .a { + -st-mixin: mix; + } + `); + + const { meta } = sheets['/entry.st.css']; + + shouldReportNoDiagnostics(meta); + }); + }); describe(`higher-level feature integrations`, () => { // ToDo: move to their higher level feature spec when created describe(`css-asset`, () => { @@ -1797,7 +1773,7 @@ describe(`features/st-mixin`, () => { shouldReportNoDiagnostics(meta); matchRuleAndDeclaration( - meta.outputAst!.nodes[2] as postcss.Container, + meta.targetAst!.nodes[2] as postcss.Container, 0, '.entry__a', 'id: nested' @@ -1832,7 +1808,7 @@ describe(`features/st-mixin`, () => { shouldReportNoDiagnostics(meta); matchRuleAndDeclaration( - meta.outputAst!.nodes[2] as postcss.Container, + meta.targetAst!.nodes[2] as postcss.Container, 0, '.entry__a', 'id: nested' @@ -1866,7 +1842,7 @@ describe(`features/st-mixin`, () => { shouldReportNoDiagnostics(meta); matchRuleAndDeclaration( - meta.outputAst!.nodes[3] as postcss.Container, + meta.targetAst!.nodes[3] as postcss.Container, 0, '.entry__a .mixin__mix', 'id: nested' @@ -1902,7 +1878,7 @@ describe(`features/st-mixin`, () => { shouldReportNoDiagnostics(meta); matchRuleAndDeclaration( - meta.outputAst!.nodes[2] as postcss.Container, + meta.targetAst!.nodes[2] as postcss.Container, 0, '.entry__a', 'id: nested' @@ -1937,7 +1913,7 @@ describe(`features/st-mixin`, () => { shouldReportNoDiagnostics(meta); matchRuleAndDeclaration( - meta.outputAst!.nodes[2] as postcss.Container, + meta.targetAst!.nodes[2] as postcss.Container, 0, '.entry__a', 'id: nested' @@ -1971,7 +1947,7 @@ describe(`features/st-mixin`, () => { shouldReportNoDiagnostics(meta); matchRuleAndDeclaration( - meta.outputAst!.nodes[3] as postcss.Container, + meta.targetAst!.nodes[3] as postcss.Container, 0, '.entry__a .mixin__mix', 'id: nested' @@ -1979,23 +1955,128 @@ describe(`features/st-mixin`, () => { }); }); }); - describe(`regressions`, () => { - it(`should not change meta.mixins during transform`, () => { - // ToDo: remove in v5 (no meta.mixins) - const { stylable, sheets } = testStylableCore( - ` - .root { -st-mixin: mix; } - .mix { color: green } - ` + describe('stylable API', () => { + it('should resolve mixin expression', () => { + const { stylable, sheets } = testStylableCore({ + '/sheet.st.css': ` + .mix-base-type { + prop: value(not-part-of-mixin); + } + .css-mix { + -st-extends: mix-base-type; + prop: value(bg-color); + } + `, + 'code.js': ` + module.exports = { + 'code-mix': ([arg1, arg2]) => { + return { [arg1]: arg2 + '!' } + }, + 'code-str': "not valid mixin", + } + `, + '/entry.st.css': ` + @st-import [css-mix as importedClassMix] from './sheet.st.css'; + @st-import [code-mix as importedFuncMix, code-str] from './code.js'; + .local-mix { + background: value(bg-size) value(bg-color); + } + .local-mix:hover { + background-color: value(bg-hover-color); + } + ElementMix { + color: value(colorList, value(color-index)); + } + :vars { + st-var-name: "cannot be used as mixin"; + } + `, + }); + const inputExpr = ` + local-mix(a 1, b value(x), c ' " , quotation and comma'), + importedClassMix(c 2), + unknownBetweenMix(e 3), + st-var-name(e 4), + ElementMix(f 5, g 6), + code-str(argX), + importedFuncMix(argA, argB), + `; + + const diagnostics = new Diagnostics(); + const resolvedMixins = stylable.stMixin.resolveExpr( + sheets['/entry.st.css'].meta, + inputExpr, + { + diagnostics, + resolveOptionalArgs: true, + } ); - const { meta } = sheets['/entry.st.css']; - stylable.transform(meta); - const mixins1 = [...meta.mixins]; - stylable.transform(meta); - const mixins2 = [...meta.mixins]; - - expect(mixins1).to.eql(mixins2); + expect(resolvedMixins[0], 'local-mix').to.eql({ + name: 'local-mix', + kind: 'css-fragment', + args: [{ a: '1' }, { b: 'value(x)' }, { c: ` " , quotation and comma` }], + optionalArgs: new Map([ + ['bg-size', { name: 'bg-size' }], + ['bg-color', { name: 'bg-color' }], + ['bg-hover-color', { name: 'bg-hover-color' }], + ]), + }); + expect(resolvedMixins[1], 'importedClassMix').to.eql({ + name: 'importedClassMix', + kind: 'css-fragment', + args: [{ c: '2' }], + optionalArgs: new Map([['bg-color', { name: 'bg-color' }]]), + }); + expect(resolvedMixins[2], 'unknownBetweenMix').to.eql({ + name: 'unknownBetweenMix', + kind: 'invalid', + args: 'e 3', + }); + expect(resolvedMixins[3], 'st-var-name').to.eql({ + name: 'st-var-name', + kind: 'invalid', + args: 'e 4', + }); + expect(resolvedMixins[4], 'ElementMix').to.eql({ + name: 'ElementMix', + kind: 'css-fragment', + args: [{ f: '5' }, { g: '6' }], + optionalArgs: new Map([ + ['colorList', { name: 'colorList' }], + ['color-index', { name: 'color-index' }], + ]), + }); + expect(resolvedMixins[5], 'code-str').to.eql({ + name: 'code-str', + kind: 'invalid', + args: 'argX', + }); + const jsMix = resolvedMixins[6]; + expect(jsMix, 'importedFuncMix').to.deep.include({ + name: 'importedFuncMix', + kind: 'js-func', + args: ['argA', 'argB'], + }); + expect( + jsMix.kind === 'js-func' && jsMix.func(['color', 'green']), + 'importedFuncMix func ref' + ).to.eql({ color: 'green!' }); + expect(diagnostics.reports, 'diagnostics').to.containSubset([ + STMixin.diagnostics.UNKNOWN_MIXIN('unknownBetweenMix'), + STMixin.diagnostics.UNSUPPORTED_MIXIN_SYMBOL('st-var-name', 'var'), + STMixin.diagnostics.JS_MIXIN_NOT_A_FUNC(), + ]); + }); + it('should combine two selectors', () => { + const { stylable } = testStylableCore(``); + const api = stylable.stMixin; + + expect(api.scopeNestedSelector('.a', '.b'), 'descendant').to.eql('.a .b'); + expect(api.scopeNestedSelector('.a', '&'), 'replacement').to.eql('.a'); + expect(api.scopeNestedSelector('.a', '&.b'), 'compound').to.eql('.a.b'); + expect(api.scopeNestedSelector('.a', ':not(&)'), 'nested').to.eql(':not(.a)'); + expect(api.scopeNestedSelector('.a', '.b&'), 'after').to.eql('.b.a'); }); }); }); diff --git a/packages/core/test/features/st-scope.spec.ts b/packages/core/test/features/st-scope.spec.ts new file mode 100644 index 000000000..6fac60bdb --- /dev/null +++ b/packages/core/test/features/st-scope.spec.ts @@ -0,0 +1,66 @@ +// import { STScope } from '@stylable/core/dist/features'; +import type { StylableMeta } from '@stylable/core'; +import { testStylableCore } from '@stylable/core-test-kit'; +import { expect } from 'chai'; +import type * as postcss from 'postcss'; + +// const STScopeDiagnostics = diagnosticBankReportToStrings(STScope.diagnostics); + +const queryBySelector = (meta: StylableMeta, selector: string): postcss.Rule | undefined => { + let match: postcss.Rule | undefined; + meta.sourceAst.walkRules((rule) => { + if (rule.selector === selector) { + match = rule; + return false; + } + return; + }); + return match; +}; +const queryStScope = (meta: StylableMeta, params: string): postcss.AtRule | undefined => { + for (const node of meta.sourceAst.nodes) { + if (node.type === 'atrule' && node.name === 'st-scope' && node.params === params) { + return node; + } + } + return; +}; + +describe(`features/st-scope`, () => { + // ToDo: move relevant tests here + + describe('stylable API', () => { + it(`should get @st-scope for rule`, () => { + const { stylable, sheets } = testStylableCore(` + @st-scope a { + .direct {} + } + @st-scope b { + @media screen { + .nested {} + } + } + top-rule { + @st-scope c { + .error-nested-scope {} + } + } + `); + + const { meta } = sheets['/entry.st.css']; + + expect( + stylable.stScope.getStScope(queryBySelector(meta, '.direct')!), + 'direct' + ).to.equal(queryStScope(meta, 'a')); + expect( + stylable.stScope.getStScope(queryBySelector(meta, '.nested')!), + 'nested' + ).to.equal(queryStScope(meta, 'b')); + expect( + stylable.stScope.getStScope(queryBySelector(meta, '.error-nested-scope')!), + 'not-top-level-scope' + ).to.equal(undefined); + }); + }); +}); diff --git a/packages/core/test/features/st-symbol.spec.ts b/packages/core/test/features/st-symbol.spec.ts index f500e8090..47616c1ff 100644 --- a/packages/core/test/features/st-symbol.spec.ts +++ b/packages/core/test/features/st-symbol.spec.ts @@ -8,13 +8,17 @@ import { ImportSymbol, KeyframesSymbol, } from '@stylable/core/dist/features'; -import { ignoreDeprecationWarn } from '@stylable/core/dist/helpers/deprecation'; import { Diagnostics } from '@stylable/core/dist/diagnostics'; -import { testStylableCore } from '@stylable/core-test-kit'; +import { diagnosticBankReportToStrings, testStylableCore } from '@stylable/core-test-kit'; import * as postcss from 'postcss'; -import { expect } from 'chai'; +import chai, { expect } from 'chai'; +import chaiSubset from 'chai-subset'; import { expectType, TypeEqual } from 'ts-expect'; +chai.use(chaiSubset); + +const stSymbolDiagnostics = diagnosticBankReportToStrings(STSymbol.diagnostics); + describe(`features/st-symbol`, () => { it(`should keep symbol on meta`, () => { const { sheets } = testStylableCore(``); @@ -29,14 +33,6 @@ describe(`features/st-symbol`, () => { expect(meta.getSymbol(`a`), `meta.getSymbol`).to.equal(STSymbol.get(meta, `a`)); expectType>(true); expectType>(false); - // deprecation - expect( - ignoreDeprecationWarn(() => meta.mappedSymbols), - `deprecated 'meta.mappedSymbols'` - ).to.eql({ - root: STSymbol.get(meta, `root`), - a: STSymbol.get(meta, `a`), - }); }); it(`should keep track of symbols by type`, () => { const { sheets } = testStylableCore(``); @@ -148,22 +144,18 @@ describe(`features/st-symbol`, () => { STSymbol.addSymbol({ context, symbol, node: ruleB }); STSymbol.reportRedeclare(context); - expect(context.diagnostics.reports).to.eql([ + expect(context.diagnostics.reports).to.containSubset([ { - type: `warning`, - message: STSymbol.diagnostics.REDECLARE_SYMBOL('a'), + severity: `warning`, + message: stSymbolDiagnostics.REDECLARE_SYMBOL('a'), node: ruleA, - options: { - word: `a`, - }, + word: `a`, }, { - type: `warning`, - message: STSymbol.diagnostics.REDECLARE_SYMBOL('a'), + severity: `warning`, + message: stSymbolDiagnostics.REDECLARE_SYMBOL('a'), node: ruleB, - options: { - word: `a`, - }, + word: `a`, }, ]); }); @@ -193,14 +185,12 @@ describe(`features/st-symbol`, () => { STSymbol.addSymbol({ context, symbol, node: rule }); - expect(context.diagnostics.reports).to.eql([ + expect(context.diagnostics.reports).to.containSubset([ { - type: `warning`, - message: STSymbol.diagnostics.REDECLARE_ROOT(), + severity: `error`, + message: stSymbolDiagnostics.REDECLARE_ROOT(), node: rule, - options: { - word: `root`, - }, + word: `root`, }, ]); }); diff --git a/packages/core/test/features/st-var.spec.ts b/packages/core/test/features/st-var.spec.ts index 4e7453c34..802493fb7 100644 --- a/packages/core/test/features/st-var.spec.ts +++ b/packages/core/test/features/st-var.spec.ts @@ -1,14 +1,21 @@ import chaiSubset from 'chai-subset'; import { STSymbol, STVar } from '@stylable/core/dist/features'; -import { functionWarnings } from '@stylable/core/dist/functions'; +import { functionDiagnostics } from '@stylable/core/dist/functions'; import { stTypes, box } from '@stylable/core/dist/custom-values'; -import { ignoreDeprecationWarn } from '@stylable/core/dist/helpers/deprecation'; -import { testStylableCore, shouldReportNoDiagnostics } from '@stylable/core-test-kit'; +import { + testStylableCore, + shouldReportNoDiagnostics, + diagnosticBankReportToStrings, +} from '@stylable/core-test-kit'; import chai, { expect } from 'chai'; import postcssValueParser from 'postcss-value-parser'; chai.use(chaiSubset); +const stSymbolDiagnostics = diagnosticBankReportToStrings(STSymbol.diagnostics); +const stVarDiagnostics = diagnosticBankReportToStrings(STVar.diagnostics); +const functionStringDiagnostics = diagnosticBankReportToStrings(functionDiagnostics); + describe(`features/st-var`, () => { const stBorderDefinitionMock = ` const { createCustomValue, CustomValueStrategy } = require("@stylable/core"); @@ -69,14 +76,6 @@ describe(`features/st-var`, () => { // JS exports expect(exports.stVars.varA, `varA JS export`).to.eql(`a-val`); expect(exports.stVars.varB, `varB JS export`).to.eql(`b-val`); - - // deprecation - ignoreDeprecationWarn(() => { - expect(meta.vars, `deprecated 'meta.vars'`).to.eql([ - STVar.get(meta, `varA`), - STVar.get(meta, `varB`), - ]); - }); }); it(`should process multiple :vars definitions`, () => { const { sheets } = testStylableCore(` @@ -117,7 +116,7 @@ describe(`features/st-var`, () => { const { sheets } = testStylableCore(` @st-scope { /* - @analyze-warn ${STVar.diagnostics.NO_VARS_DEF_IN_ST_SCOPE()} + @analyze-error ${stVarDiagnostics.NO_VARS_DEF_IN_ST_SCOPE()} @transform-remove */ :vars { @@ -125,7 +124,7 @@ describe(`features/st-var`, () => { } } - /* @analyze-warn(complex selector) ${STVar.diagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR( + /* @analyze-error(complex selector) ${stVarDiagnostics.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR( `:vars` )} */ .root:vars {} @@ -190,25 +189,25 @@ describe(`features/st-var`, () => { .root { /* @decl(empty) prop: value() - @transform-warn(empty) ${STVar.diagnostics.MISSING_VAR_IN_VALUE()} + @transform-error(empty) ${stVarDiagnostics.MISSING_VAR_IN_VALUE()} */ prop: value(); /* @decl(no first arg) prop: value(, path) - @transform-warn(no first arg) ${STVar.diagnostics.MISSING_VAR_IN_VALUE()} + @transform-error(no first arg) ${stVarDiagnostics.MISSING_VAR_IN_VALUE()} */ prop: value(, path); /* @decl(unknown var) prop: value(unknown) - @transform-warn(unknown var) ${STVar.diagnostics.UNKNOWN_VAR('unknown')} + @transform-error(unknown var) ${stVarDiagnostics.UNKNOWN_VAR('unknown')} */ prop: value(unknown); /* @decl(non var symbol) prop: value(part) - @transform-warn(non var symbol) word(part) ${STVar.diagnostics.CANNOT_USE_AS_VALUE( + @transform-error(non var symbol) word(part) ${stVarDiagnostics.CANNOT_USE_AS_VALUE( 'class', `part` )} @@ -217,7 +216,7 @@ describe(`features/st-var`, () => { /* @decl(unknown 2nd arg) prop: green - @transform-warn(unknown 2nd arg) ${STVar.diagnostics.MULTI_ARGS_IN_VALUE( + @transform-error(unknown 2nd arg) ${stVarDiagnostics.MULTI_ARGS_IN_VALUE( 'varA, invalidSecondArgument' )} */ @@ -230,19 +229,15 @@ describe(`features/st-var`, () => { meta.transformDiagnostics!.reports[0], `missing var diagnostic word 1` ).to.deep.contain({ - message: STVar.diagnostics.MISSING_VAR_IN_VALUE(), - options: { - word: `value()`, - }, + message: stVarDiagnostics.MISSING_VAR_IN_VALUE(), + word: `value()`, }); expect( meta.transformDiagnostics!.reports[1], `missing var diagnostic word 2` ).to.deep.contain({ - message: STVar.diagnostics.MISSING_VAR_IN_VALUE(), - options: { - word: `value(, path)`, - }, + message: stVarDiagnostics.MISSING_VAR_IN_VALUE(), + word: `value(, path)`, }); }); it(`should handle escaping`, () => { @@ -330,14 +325,14 @@ describe(`features/st-var`, () => { it(`should resolve cyclic vars`, () => { const { sheets } = testStylableCore(` :vars { - /* @transform-warn(varA) ${STVar.diagnostics.CYCLIC_VALUE([ + /* @transform-error(varA) ${stVarDiagnostics.CYCLIC_VALUE([ `/entry.st.css: varB`, `/entry.st.css: varA`, `/entry.st.css: varB`, ])} */ varA: a(value(varB)); - /* @transform-warn(varB) ${STVar.diagnostics.CYCLIC_VALUE([ + /* @transform-error(varB) ${stVarDiagnostics.CYCLIC_VALUE([ `/entry.st.css: varA`, `/entry.st.css: varB`, `/entry.st.css: varA`, @@ -610,10 +605,10 @@ describe(`features/st-var`, () => { /* @decl(success despite internal path error) prop: deep-value - @transform-warn(index un-required path) ${STVar.diagnostics.MULTI_ARGS_IN_VALUE( + @transform-error(index un-required path) ${stVarDiagnostics.MULTI_ARGS_IN_VALUE( 'arr-key, no-path' )} - @transform-warn(key un-required path) ${STVar.diagnostics.MULTI_ARGS_IN_VALUE( + @transform-error(key un-required path) ${stVarDiagnostics.MULTI_ARGS_IN_VALUE( 'map-key, no-path' )} */ @@ -632,12 +627,12 @@ describe(`features/st-var`, () => { ); } .root { - /* @transform-warn(1st level) ${STVar.diagnostics.COULD_NOT_RESOLVE_VALUE( + /* @transform-error(1st level) ${stVarDiagnostics.COULD_NOT_RESOLVE_VALUE( `myVar, unknown` )}*/ prop: value(myVar, unknown); - /* @transform-warn(2nd level) ${STVar.diagnostics.COULD_NOT_RESOLVE_VALUE( + /* @transform-error(2nd level) ${stVarDiagnostics.COULD_NOT_RESOLVE_VALUE( `myVar, key2, unknown` )}*/ prop: value(myVar, key2, unknown); @@ -653,19 +648,19 @@ describe(`features/st-var`, () => { it(`should report deprecated forms`, () => { testStylableCore(` :vars { - /* @analyze-info(stMap) word(stMap) ${STVar.diagnostics.DEPRECATED_ST_FUNCTION_NAME( + /* @analyze-info(stMap) word(stMap) ${stVarDiagnostics.DEPRECATED_ST_FUNCTION_NAME( `stMap`, `st-map` )}*/ varA: stMap(a 1); - /* @analyze-info(stArray) word(stArray) ${STVar.diagnostics.DEPRECATED_ST_FUNCTION_NAME( + /* @analyze-info(stArray) word(stArray) ${stVarDiagnostics.DEPRECATED_ST_FUNCTION_NAME( `stArray`, `st-array` )}*/ varA: stArray(a, b); - /* @analyze-info(nested) word(stMap) ${STVar.diagnostics.DEPRECATED_ST_FUNCTION_NAME( + /* @analyze-info(nested) word(stMap) ${stVarDiagnostics.DEPRECATED_ST_FUNCTION_NAME( `stMap`, `st-map` )}*/ @@ -683,7 +678,7 @@ describe(`features/st-var`, () => { testStylableCore(` :vars { /* @transform-error ${ - STVar.diagnostics.COULD_NOT_RESOLVE_VALUE( + stVarDiagnostics.COULD_NOT_RESOLVE_VALUE( `keyWithoutValue` ) /** TODO - add custom diagnostic for this case */ }*/ @@ -888,7 +883,7 @@ describe(`features/st-var`, () => { .root { /* @decl(imported sheet) prop: value(Sheet) - @transform-warn(imported sheet) word(Sheet) ${STVar.diagnostics.CANNOT_USE_AS_VALUE( + @transform-error(imported sheet) word(Sheet) ${stVarDiagnostics.CANNOT_USE_AS_VALUE( `stylesheet`, `Sheet` )} @@ -897,7 +892,7 @@ describe(`features/st-var`, () => { /* @decl(imported-class) prop: value(imported-class) - @transform-warn(imported-class) word(imported-class) ${STVar.diagnostics.CANNOT_USE_AS_VALUE( + @transform-error(imported-class) word(imported-class) ${stVarDiagnostics.CANNOT_USE_AS_VALUE( `class`, `imported-class` )} @@ -906,7 +901,7 @@ describe(`features/st-var`, () => { /* @decl(unknown) prop: value(unknown) - @transform-error(unknown) word(unknown) ${STVar.diagnostics.UNKNOWN_VAR( + @transform-error(unknown) word(unknown) ${stVarDiagnostics.UNKNOWN_VAR( `unknown` )} */ @@ -914,7 +909,7 @@ describe(`features/st-var`, () => { /* @decl(JS number) prop: value(jsNum) - @transform-warn(JS number) word(jsNum) ${STVar.diagnostics.CANNOT_USE_JS_AS_VALUE( + @transform-error(JS number) word(jsNum) ${stVarDiagnostics.CANNOT_USE_JS_AS_VALUE( `number`, `jsNum` )} @@ -923,7 +918,7 @@ describe(`features/st-var`, () => { /* @decl(JS function) prop: value(jsFunc) - @transform-warn(JS function) word(jsFunc) ${STVar.diagnostics.CANNOT_USE_JS_AS_VALUE( + @transform-error(JS function) word(jsFunc) ${stVarDiagnostics.CANNOT_USE_JS_AS_VALUE( `function`, `jsFunc` )} @@ -943,7 +938,7 @@ describe(`features/st-var`, () => { `, '/entry.st.css': ` :vars { - /* @analyze-warn(local before) word(before) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + /* @analyze-warn(local before) word(before) ${stSymbolDiagnostics.REDECLARE_SYMBOL( `before` )} */ before: local-before-val; @@ -954,17 +949,17 @@ describe(`features/st-var`, () => { } /* - @analyze-warn(import before) word(before) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + @analyze-warn(import before) word(before) ${stSymbolDiagnostics.REDECLARE_SYMBOL( `before` )} - @analyze-warn(import after) word(after) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + @analyze-warn(import after) word(after) ${stSymbolDiagnostics.REDECLARE_SYMBOL( `after` )} */ @st-import [before, after] from './vars.st.css'; :vars { - /* @analyze-warn(local after) word(after) ${STSymbol.diagnostics.REDECLARE_SYMBOL( + /* @analyze-warn(local after) word(after) ${stSymbolDiagnostics.REDECLARE_SYMBOL( `after` )} */ after: local-after-val; @@ -1026,7 +1021,7 @@ describe(`features/st-var`, () => { '/a.st.css': ` @st-import [varB] from './b.st.css'; :vars { - /* @transform-warn(varA) ${STVar.diagnostics.CYCLIC_VALUE([ + /* @transform-error(varA) ${stVarDiagnostics.CYCLIC_VALUE([ `/a.st.css: varB`, `/b.st.css: varA`, `/a.st.css: varB`, @@ -1044,7 +1039,7 @@ describe(`features/st-var`, () => { '/b.st.css': ` @st-import [varA] from './a.st.css'; :vars { - /* @transform-warn(varB) ${STVar.diagnostics.CYCLIC_VALUE([ + /* @transform-error(varB) ${stVarDiagnostics.CYCLIC_VALUE([ `/b.st.css: varA`, `/a.st.css: varB`, `/b.st.css: varA`, @@ -1596,8 +1591,9 @@ describe(`features/st-var`, () => { diagnostics: { reports: [ { - message: functionWarnings.UNKNOWN_FORMATTER('invalid-func'), - type: 'warning', + message: + functionStringDiagnostics.UNKNOWN_FORMATTER('invalid-func'), + severity: 'error', }, ], }, @@ -1631,8 +1627,8 @@ describe(`features/st-var`, () => { diagnostics: { reports: [ { - message: STVar.diagnostics.COULD_NOT_RESOLVE_VALUE(), - type: 'warning', + message: stVarDiagnostics.COULD_NOT_RESOLVE_VALUE(), + severity: 'error', }, ], }, diff --git a/packages/core/test/functions.spec.ts b/packages/core/test/functions.spec.ts index fa9f93c3c..97bae070a 100644 --- a/packages/core/test/functions.spec.ts +++ b/packages/core/test/functions.spec.ts @@ -1,8 +1,15 @@ -import { functionWarnings, nativeFunctionsDic } from '@stylable/core'; -import { expectTransformDiagnostics, generateStylableRoot } from '@stylable/core-test-kit'; +import { functionDiagnostics } from '@stylable/core/dist/functions'; +import { nativeFunctionsDic } from '@stylable/core/dist/native-reserved-lists'; +import { + diagnosticBankReportToStrings, + expectTransformDiagnostics, + generateStylableRoot, +} from '@stylable/core-test-kit'; import { expect } from 'chai'; import type * as postcss from 'postcss'; +const functionStringDiagnostics = diagnosticBankReportToStrings(functionDiagnostics); + // var receives special handling and standalone testing const testedNativeFunctions = Object.keys(nativeFunctionsDic).filter((func) => func !== 'var'); @@ -522,7 +529,10 @@ describe('Stylable functions (native, formatter and variable)', () => { }; expectTransformDiagnostics(config, [ - { message: functionWarnings.UNKNOWN_FORMATTER(key), file: '/main.st.css' }, + { + message: functionStringDiagnostics.UNKNOWN_FORMATTER(key), + file: '/main.st.css', + }, ]); }); @@ -556,7 +566,7 @@ describe('Stylable functions (native, formatter and variable)', () => { expectTransformDiagnostics(config, [ { - message: functionWarnings.FAIL_TO_EXECUTE_FORMATTER( + message: functionStringDiagnostics.FAIL_TO_EXECUTE_FORMATTER( 'fail(a, red, c)', 'FAIL FAIL FAIL' ), diff --git a/packages/core/test/helpers/custom-selector.spec.ts b/packages/core/test/helpers/custom-selector.spec.ts new file mode 100644 index 000000000..16083b34d --- /dev/null +++ b/packages/core/test/helpers/custom-selector.spec.ts @@ -0,0 +1,252 @@ +import { + transformCustomSelectors, + transformCustomSelectorMap, + CustomSelectorMap, + TransformCustomSelectorReport, +} from '@stylable/core/dist/helpers/custom-selector'; +import { parseCssSelector, stringifySelectorAst } from '@tokey/css-selector-parser'; +import { expect } from 'chai'; + +const noopReport = () => { + /**/ +}; + +describe('helpers/custom-selector', () => { + describe('transformCustomSelectorMap', () => { + it('should inline references', () => { + const reports: TransformCustomSelectorReport[] = []; + + const selectors = transformCustomSelectorMap( + { + x: parseCssSelector(':is(target)'), + y: parseCssSelector(':--x.y'), + z: parseCssSelector(':--y.z'), + }, + (report) => reports.push(report) + ); + + expect(stringifySelectorAst(selectors['x']), ':--x').to.eql(':is(target)'); + expect(stringifySelectorAst(selectors['y']), ':--y').to.eql(':is(target).y'); + expect(stringifySelectorAst(selectors['z']), ':--z').to.eql(':is(target).y.z'); + expect(reports, 'no circularity').to.eql([]); + }); + it('should handle unknown custom selector', () => { + const reports: TransformCustomSelectorReport[] = []; + + const selectors = transformCustomSelectorMap( + { + x: parseCssSelector(':--unknown'), + }, + (report) => reports.push(report) + ); + + expect(stringifySelectorAst(selectors['x']), 'ref').to.eql(':--unknown'); + expect(reports, 'unknown selector').to.eql([ + { type: 'unknown', origin: 'x', unknown: 'unknown' }, + ]); + }); + it('should report circular self reference', () => { + const reports: TransformCustomSelectorReport[] = []; + + const selectors = transformCustomSelectorMap( + { + x: parseCssSelector(':--x, :is(:--x, :--y)'), + y: parseCssSelector('.A'), + }, + (report) => reports.push(report) + ); + + expect(stringifySelectorAst(selectors['x']), ':--x').to.eql(':--x, :is(:--x, .A)'); + expect(stringifySelectorAst(selectors['y']), ':--y').to.eql('.A'); + expect(reports, 'circularity reports').to.eql([ + { type: 'circular', path: [':--x'] }, // first + { type: 'circular', path: [':--x'] }, // second + ]); // ToDo: maybe don't report 2 identical paths + }); + it('should report circular deep reference', () => { + const reports: TransformCustomSelectorReport[] = []; + + const selectors = transformCustomSelectorMap( + { + x: parseCssSelector(':is(:--z):--a'), + y: parseCssSelector(':--x.y'), + z: parseCssSelector(':--y.z'), + a: parseCssSelector('.A'), + }, + (report) => reports.push(report) + ); + + expect(stringifySelectorAst(selectors['x']), ':--x').to.eql(':is(:--x.y.z).A'); + expect(stringifySelectorAst(selectors['y']), ':--y').to.eql(':is(:--x.y.z).A.y'); + expect(stringifySelectorAst(selectors['z']), ':--z').to.eql(':is(:--x.y.z).A.y.z'); + expect(reports, 'circularity reports').to.eql([ + { type: 'circular', path: [':--x', ':--z', ':--y'] }, // only the first custom selector found in loop + ]); + }); + }); + describe('transformCustomSelectors', () => { + it('should preserve selector without custom selector', () => { + const input = parseCssSelector('.a:is(.b, :not(.c))'); + const selectors: CustomSelectorMap = {}; + + const output = transformCustomSelectors( + input, + (name: string) => selectors[name], + noopReport + ); + + expect(output).to.eql(input); + }); + it('should permute for each selector', () => { + const input = parseCssSelector('.before:--x.after'); + const selectors: CustomSelectorMap = { + x: parseCssSelector('.A, .B'), + }; + + const output = transformCustomSelectors( + input, + (name: string) => selectors[name], + noopReport + ); + + expect(stringifySelectorAst(output)).to.eql('.before.A.after,.before.B.after'); + }); + it('should permute for each selector from multiple custom selectors', () => { + const input = parseCssSelector(':--x:--y'); + const selectors: CustomSelectorMap = { + x: parseCssSelector('.A, .B'), + y: parseCssSelector('.C, .D'), + }; + + const output = transformCustomSelectors( + input, + (name: string) => selectors[name], + noopReport + ); + + expect(stringifySelectorAst(output)).to.eql('.A.C,.B.C,.A.D,.B.D'); + }); + it('should permute for each selector from multiple custom selectors (x3)', () => { + const input = parseCssSelector(':--x:--y:--z'); + const selectors: CustomSelectorMap = { + x: parseCssSelector('.A, .B'), + y: parseCssSelector('.C, .D'), + z: parseCssSelector('.E, .F'), + }; + + const output = transformCustomSelectors( + input, + (name: string) => selectors[name], + noopReport + ); + + expect(stringifySelectorAst(output)).to.eql( + [ + '.A.C.E', + '.B.C.E', + '.A.D.E', + '.B.D.E', + '.A.C.F', + '.B.C.F', + '.A.D.F', + '.B.D.F', + ].join(',') + ); + }); + it('should permute from deep selectors', () => { + const input = parseCssSelector(':is(:--x):not(:--y)'); + const selectors: CustomSelectorMap = { + x: parseCssSelector('.AA, .BB'), + y: parseCssSelector('.CC, .DD'), + }; + + const output = transformCustomSelectors( + input, + (name: string) => selectors[name], + noopReport + ); + + expect(stringifySelectorAst(output)).to.eql( + [ + ':is(.AA):not(.CC)', + ':is(.BB):not(.CC)', + ':is(.AA):not(.DD)', + ':is(.BB):not(.DD)', + ].join(',') + ); + }); + it('should permute multi selector input', () => { + const input = parseCssSelector(':--x,:--y'); + const selectors: CustomSelectorMap = { + x: parseCssSelector('.A, .B'), + y: parseCssSelector('.C, .D'), + }; + + const output = transformCustomSelectors( + input, + (name: string) => selectors[name], + noopReport + ); + + expect(stringifySelectorAst(output)).to.eql('.A,.B,.C,.D'); + }); + it('should deep replace custom selectors', () => { + const input = parseCssSelector(':--y'); + const selectors = transformCustomSelectorMap( + { + x: parseCssSelector('.A'), + y: parseCssSelector(':--x'), + }, + noopReport + ); + + const output = transformCustomSelectors( + input, + (name: string) => selectors[name], + noopReport + ); + + expect(stringifySelectorAst(output)).to.eql('.A'); + }); + it('should handle unknown name', () => { + const input = parseCssSelector(':--x'); + const reports: TransformCustomSelectorReport[] = []; + + const output = transformCustomSelectors( + input, + (_name: string) => undefined, + (data) => reports.push(data) + ); + + expect(stringifySelectorAst(output), 'transform').to.eql(':--x'); + expect(reports, 'reports').to.eql([{ type: 'unknown', origin: '', unknown: 'x' }]); + }); + it('should handle circular reference', () => { + const inputX = parseCssSelector(':--x'); + const inputY = parseCssSelector(':--y'); + const selectors = transformCustomSelectorMap( + { + x: parseCssSelector(':--y'), + y: parseCssSelector(':--x'), + }, + (_path) => { + /*circular report*/ + } + ); + + const outputX = transformCustomSelectors( + inputX, + (name: string) => selectors[name], + noopReport + ); + const outputY = transformCustomSelectors( + inputY, + (name: string) => selectors[name], + noopReport + ); + + expect(stringifySelectorAst(outputX), ':--x').to.eql(':--x'); + expect(stringifySelectorAst(outputY), ':--y').to.eql(':--x'); + }); + }); +}); diff --git a/packages/core/test/helpers/import.spec.ts b/packages/core/test/helpers/import.spec.ts index 90e830533..01f99749e 100644 --- a/packages/core/test/helpers/import.spec.ts +++ b/packages/core/test/helpers/import.spec.ts @@ -1,8 +1,16 @@ import { expect } from 'chai'; import { parse } from 'postcss'; import { Diagnostics, ensureModuleImport } from '@stylable/core'; -import { ensureImportsMessages, parsePseudoImportNamed } from '@stylable/core/dist/helpers/import'; +import { + ensureImportsMessages, + parseImportMessages, + parsePseudoImportNamed, +} from '@stylable/core/dist/helpers/import'; import * as postcss from 'postcss'; +import { diagnosticBankReportToStrings } from '@stylable/core-test-kit'; + +const parseImportsDiagnostics = diagnosticBankReportToStrings(parseImportMessages); +const ensureImportsDiagnostics = diagnosticBankReportToStrings(ensureImportsMessages); describe(`helpers/import`, () => { describe('ensureModuleImport', () => { @@ -39,7 +47,7 @@ describe(`helpers/import`, () => { expect(diag.reports, 'diagnostics').to.have.lengthOf(1); expect(diag.reports[0].message).to.equal( - ensureImportsMessages.PATCH_CONTAINS_NEW_IMPORT_IN_NEW_IMPORT_NONE_MODE() + ensureImportsDiagnostics.PATCH_CONTAINS_NEW_IMPORT_IN_NEW_IMPORT_NONE_MODE() ); expect(root.nodes, 'no imports added').to.have.lengthOf(0); }); @@ -465,7 +473,7 @@ describe(`helpers/import`, () => { expect(importNode.toString(), 'no change').to.equal(`@st-import Test from "x"`); expect(diagnostics.reports, 'diagnostics').to.have.lengthOf(1); expect(diagnostics.reports[0].message).to.equal( - ensureImportsMessages.ATTEMPT_OVERRIDE_SYMBOL('default', 'Test', 'Y') + ensureImportsDiagnostics.ATTEMPT_OVERRIDE_SYMBOL('default', 'Test', 'Y') ); }); it('should report collision diagnostics for named and not patch', () => { @@ -481,7 +489,7 @@ describe(`helpers/import`, () => { expect(importNode.toString(), 'no change').to.equal(`@st-import [Y] from "x"`); expect(diagnostics.reports, 'diagnostics').to.have.lengthOf(1); expect(diagnostics.reports[0].message).to.equal( - ensureImportsMessages.ATTEMPT_OVERRIDE_SYMBOL('named', 'Y', 'X as Y') + ensureImportsDiagnostics.ATTEMPT_OVERRIDE_SYMBOL('named', 'Y', 'X as Y') ); }); it('should report collision diagnostics for named "as" with "as" (no patch)', () => { @@ -497,7 +505,7 @@ describe(`helpers/import`, () => { expect(importNode.toString(), 'no change').to.equal(`@st-import [A as Y] from "x"`); expect(diagnostics.reports, 'diagnostics').to.have.lengthOf(1); expect(diagnostics.reports[0].message).to.equal( - ensureImportsMessages.ATTEMPT_OVERRIDE_SYMBOL('named', 'A as Y', 'X as Y') + ensureImportsDiagnostics.ATTEMPT_OVERRIDE_SYMBOL('named', 'A as Y', 'X as Y') ); }); it('should report collision diagnostics for named "as" (no patch)', () => { @@ -513,7 +521,7 @@ describe(`helpers/import`, () => { expect(importNode.toString(), 'no change').to.equal(`@st-import [A as Y] from "x"`); expect(diagnostics.reports, 'diagnostics').to.have.lengthOf(1); expect(diagnostics.reports[0].message).to.equal( - ensureImportsMessages.ATTEMPT_OVERRIDE_SYMBOL('named', 'A as Y', 'Y') + ensureImportsDiagnostics.ATTEMPT_OVERRIDE_SYMBOL('named', 'A as Y', 'Y') ); }); }); @@ -613,7 +621,7 @@ describe(`helpers/import`, () => { ); expect(diagnostics.reports).to.be.lengthOf(1); expect(diagnostics.reports[0].message).to.equal( - 'Invalid named import "as" with name "x"' + parseImportsDiagnostics.INVALID_NAMED_IMPORT_AS('x') ); }); it('invalid nested keyframes', () => { @@ -626,7 +634,7 @@ describe(`helpers/import`, () => { ); expect(diagnostics.reports).to.be.lengthOf(1); expect(diagnostics.reports[0].message).to.equal( - 'Invalid nested keyframes import "keyframes(b)"' + parseImportsDiagnostics.INVALID_NESTED_KEYFRAMES('keyframes(b)') ); }); }); diff --git a/packages/core/test/helpers/mixin.spec.ts b/packages/core/test/helpers/mixin.spec.ts index 1abdabccd..6108c5805 100644 --- a/packages/core/test/helpers/mixin.spec.ts +++ b/packages/core/test/helpers/mixin.spec.ts @@ -1,18 +1,18 @@ import { expect } from 'chai'; import * as postcss from 'postcss'; -import { SBTypesParsers } from '@stylable/core'; +import { parseStMixin, parseStPartialMixin } from '@stylable/core/dist/helpers/mixin'; import postcssValueParser from 'postcss-value-parser'; const createMixinDecl = (value: string) => postcss.decl({ prop: `-st-mixin`, value }); const createPartialMixinDecl = (value: string) => postcss.decl({ prop: `-st-partial-mixin`, value }); const parseMixin = (mixinDecl: postcss.Declaration) => { - const mix = SBTypesParsers[`-st-mixin`](mixinDecl, () => 'named'); + const mix = parseStMixin(mixinDecl, () => 'named'); return mix; }; const parsePartialMixin = (mixinDecl: postcss.Declaration) => { - const mix = SBTypesParsers[`-st-partial-mixin`](mixinDecl, () => 'named'); + const mix = parseStPartialMixin(mixinDecl, () => 'named'); return mix; }; diff --git a/packages/core/test/helpers/rule.spec.ts b/packages/core/test/helpers/rule.spec.ts index 1b79bc0b3..ff14f8075 100644 --- a/packages/core/test/helpers/rule.spec.ts +++ b/packages/core/test/helpers/rule.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { createSubsetAst } from '@stylable/core/dist/helpers/rule'; -import { cssParse } from '@stylable/core'; +import { cssParse } from '@stylable/core/dist/index-internal'; describe(`helpers/rule`, () => { describe('createSubsetAst', () => { diff --git a/packages/core/test/intellisense/intellisense.spec.ts b/packages/core/test/intellisense/intellisense.spec.ts index 35f297674..8a4a026e0 100644 --- a/packages/core/test/intellisense/intellisense.spec.ts +++ b/packages/core/test/intellisense/intellisense.spec.ts @@ -1,7 +1,6 @@ import { expect } from 'chai'; -import * as postcss from 'postcss'; import { createTransformer } from '@stylable/core-test-kit'; -import { expandCustomSelectors } from '@stylable/core'; +import { STCustomSelector } from '@stylable/core/dist/index-internal'; describe('Stylable intellisense selector meta data', () => { it('resolve single class element', () => { @@ -391,7 +390,7 @@ describe('Stylable intellisense selector meta data', () => { const meta = t.fileProcessor.process('/entry.st.css'); const elements = t.resolveSelectorElements( meta, - expandCustomSelectors(postcss.rule({ selector: ':--pongo' }), meta.customSelectors) + STCustomSelector.transformCustomSelectorInline(meta, ':--pongo') ); expect(elements[0]).to.eql([ diff --git a/packages/core/test/pseudo-states.spec.ts b/packages/core/test/pseudo-states.spec.ts index 2df93e4ad..3144bd8b9 100644 --- a/packages/core/test/pseudo-states.spec.ts +++ b/packages/core/test/pseudo-states.spec.ts @@ -9,9 +9,11 @@ import { generateStylableResult, processSource, testInlineExpects, + diagnosticBankReportToStrings, } from '@stylable/core-test-kit'; -import { processorWarnings, nativePseudoClasses, pseudoStates } from '@stylable/core'; +import { processorDiagnostics, nativePseudoClasses } from '@stylable/core/dist/index-internal'; import { reservedFunctionalPseudoClasses } from '@stylable/core/dist/native-reserved-lists'; +import { stateDiagnostics } from '@stylable/core/dist/pseudo-states'; import { CSSType } from '@stylable/core/dist/features'; chai.use(chaiSubset); // move all of these to a central place @@ -19,12 +21,14 @@ chai.use(styleRules); chai.use(mediaQuery); chai.use(flatMatch); -const { stateErrors } = pseudoStates; - // testing concerns for feature // - states belonging to an extended class (multi level) // - lookup order +const stateStringDiagnostics = diagnosticBankReportToStrings(stateDiagnostics); +const cssTypeDiagnostics = diagnosticBankReportToStrings(CSSType.diagnostics); +const processorStringDiagnostics = diagnosticBankReportToStrings(processorDiagnostics); + describe('pseudo-states', () => { describe('process', () => { // What does it do? @@ -52,7 +56,7 @@ describe('pseudo-states', () => { }`, [ { - message: stateErrors.RESERVED_NATIVE_STATE(name), + message: stateStringDiagnostics.RESERVED_NATIVE_STATE(name), file: 'main.css', }, ] @@ -114,7 +118,7 @@ describe('pseudo-states', () => { `, [ { - message: stateErrors.TOO_MANY_STATE_TYPES('state1', [ + message: stateStringDiagnostics.TOO_MANY_STATE_TYPES('state1', [ 'string', 'number(x)', ]), @@ -133,7 +137,7 @@ describe('pseudo-states', () => { `, [ { - message: stateErrors.NO_STATE_TYPE_GIVEN('state1'), + message: stateStringDiagnostics.NO_STATE_TYPE_GIVEN('state1'), file: 'main.css', }, ] @@ -149,10 +153,11 @@ describe('pseudo-states', () => { `, [ { - message: stateErrors.TOO_MANY_ARGS_IN_VALIDATOR('state1', 'contains', [ - 'one', - 'two', - ]), + message: stateStringDiagnostics.TOO_MANY_ARGS_IN_VALIDATOR( + 'state1', + 'contains', + ['one', 'two'] + ), file: 'main.css', }, ] @@ -168,7 +173,7 @@ describe('pseudo-states', () => { `, [ { - message: stateErrors.UNKNOWN_STATE_TYPE('state1', 'unknown'), + message: stateStringDiagnostics.UNKNOWN_STATE_TYPE('state1', 'unknown'), file: 'main.css', }, ] @@ -475,54 +480,6 @@ describe('pseudo-states', () => { }); }); }); - - describe('tag', () => { - it('as a simple validator', () => { - const res = processSource( - ` - .root { - -st-states: category(tag); - } - `, - { from: 'path/to/style.css' } - ); - - expect(res.diagnostics.reports.length, 'no reports').to.eql(0); - - expect(res.getAllClasses()).to.containSubset({ - root: { - '-st-states': { - category: { - type: 'tag', - }, - }, - }, - }); - }); - - it('including a default value', () => { - const res = processSource( - ` - .root { - -st-states: category(tag) movie; - } - `, - { from: 'path/to/style.css' } - ); - - expect(res.diagnostics.reports.length, 'no reports').to.eql(0); - expect(res.getAllClasses()).to.containSubset({ - root: { - '-st-states': { - category: { - defaultValue: 'movie', - type: 'tag', - }, - }, - }, - }); - }); - }); }); describe('custom mapping', () => { @@ -705,35 +662,6 @@ describe('pseudo-states', () => { }); describe('advanced type / validation', () => { - xit('should default to a boolean state when state is a function but receives no type', () => { - // TODO: Make this pass? - - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .my-class| { - -st-states: |state1|(); - } - .my-class:state1 {} - `, - }, - }, - }); - - // const res = expectTransformDiagnostics(config, [{ - // message: [ - // 'pseudo-state "state1" expected a definition of a single type, but received none' - // ].join('\n'), - // file: '/entry.st.css' - // }]); - expect(res).to.have.styleRules({ - 1: '.entry__my-class[data-entry-state1] {}', - }); - }); - it('should strip quotation marks when transform any state parameter', () => { const res = generateStylableResult({ entry: `/entry.st.css`, @@ -1393,7 +1321,7 @@ describe('pseudo-states', () => { }, }); - testInlineExpects(res.meta.outputAst!); + testInlineExpects(res.meta.targetAst!); expect( res.meta.diagnostics.reports, 'no diagnostics reported for native states' @@ -1698,91 +1626,6 @@ describe('pseudo-states', () => { }); }); }); - - describe('tag', () => { - it('should transform tag validator', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .my-class { - -st-states: category( tag ); - } - .my-class:category(movie) {} - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - expect(res).to.have.styleRules({ - 1: '.entry__my-class.entry---category-5-movie {}', - }); - }); - - it('should transform tag validator with a variable in its usage', () => { - const res = generateStylableResult({ - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - :vars { - category: disco; - } - .my-class { - -st-states: category( tag() ); - } - .my-class:category(value(category)) {} - `, - }, - }, - }); - - expect( - res.meta.diagnostics.reports, - 'no diagnostics reported for native states' - ).to.eql([]); - expect(res).to.have.styleRules({ - 1: '.entry__my-class.entry---category-5-disco {}', - }); - }); - - it('should warn when a value includes a space', () => { - const config = { - entry: `/entry.st.css`, - files: { - '/entry.st.css': { - namespace: 'entry', - content: ` - .my-class { - -st-states: category( tag ); - } - |.my-class:category($one two$)| {} - `, - }, - }, - }; - - const res = expectTransformDiagnostics(config, [ - { - message: [ - 'pseudo-state "category" with parameter "one two" failed validation:', - 'expected "one two" to be a single value with no spaces', - ].join('\n'), - file: '/entry.st.css', - }, - ]); - expect(res).to.have.styleRules({ - 1: '.entry__my-class.entry---category-7-one_two {}', - }); - }); - }); }); describe('custom mapping', () => { @@ -2177,7 +2020,6 @@ describe('pseudo-states', () => { }, }); - // result.meta.outputAst.toString(); expect( result.meta.diagnostics.reports, 'no diagnostics reported for imported states' @@ -2225,9 +2067,9 @@ describe('pseudo-states', () => { const res = expectTransformDiagnostics(config, [ { - message: stateErrors.NO_STATE_ARGUMENT_GIVEN('state1', 'string'), + message: stateStringDiagnostics.NO_STATE_ARGUMENT_GIVEN('state1', 'string'), file: '/entry.st.css', - severity: 'warning', + severity: 'error', }, ]); @@ -2254,9 +2096,9 @@ describe('pseudo-states', () => { const res = expectTransformDiagnostics(config, [ { - message: stateErrors.NO_STATE_ARGUMENT_GIVEN('state1', 'string'), + message: stateStringDiagnostics.NO_STATE_ARGUMENT_GIVEN('state1', 'string'), file: '/entry.st.css', - severity: 'warning', + severity: 'error', }, ]); @@ -2277,7 +2119,10 @@ describe('pseudo-states', () => { }; const res = expectTransformDiagnostics(config, [ - { message: stateErrors.UNKNOWN_STATE_USAGE('unknownState'), file: '/entry.st.css' }, + { + message: stateStringDiagnostics.UNKNOWN_STATE_USAGE('unknownState'), + file: '/entry.st.css', + }, ]); expect(res, 'keep unknown state').to.have.styleRules([`.entry__root:unknownState{}`]); }); @@ -2309,12 +2154,12 @@ describe('pseudo-states', () => { [ // skipping root scoping warning { - message: CSSType.diagnostics.UNSCOPED_TYPE_SELECTOR('MyElement'), + message: cssTypeDiagnostics.UNSCOPED_TYPE_SELECTOR('MyElement'), file: 'main.css', skip: true, }, { - message: processorWarnings.STATE_DEFINITION_IN_ELEMENT(), + message: processorStringDiagnostics.STATE_DEFINITION_IN_ELEMENT(), file: 'main.css', }, ] @@ -2331,7 +2176,15 @@ describe('pseudo-states', () => { |-st-states: mystate2;| } `, - [{ message: 'override "-st-states" on typed rule "root"', file: 'main.css' }] + [ + { + message: processorStringDiagnostics.OVERRIDE_TYPED_RULE( + '-st-states', + 'root' + ), + file: 'main.css', + }, + ] ); }); @@ -2344,7 +2197,7 @@ describe('pseudo-states', () => { `, [ { - message: stateErrors.STATE_STARTS_WITH_HYPHEN('-someState'), + message: stateStringDiagnostics.STATE_STARTS_WITH_HYPHEN('-someState'), file: 'main.css', severity: 'error', }, diff --git a/packages/core/test/scope-directive.spec.ts b/packages/core/test/scope-directive.spec.ts index 70eeca115..aa467884b 100644 --- a/packages/core/test/scope-directive.spec.ts +++ b/packages/core/test/scope-directive.spec.ts @@ -1,15 +1,19 @@ import { expect, use } from 'chai'; import type { AtRule, Declaration, Rule } from 'postcss'; import { + diagnosticBankReportToStrings, expectTransformDiagnostics, flatMatch, generateStylableResult, processSource, shouldReportNoDiagnostics, } from '@stylable/core-test-kit'; -import { SRule, transformerWarnings, getRuleScopeSelector } from '@stylable/core'; +import { transformerDiagnostics } from '@stylable/core/dist/index-internal'; import { STScope } from '@stylable/core/dist/features'; +const stScopeDiagnostics = diagnosticBankReportToStrings(STScope.diagnostics); +const transformerStringDiagnostics = diagnosticBankReportToStrings(transformerDiagnostics); + use(flatMatch); // ToDo: refactor into feature spec @@ -34,23 +38,6 @@ describe('@st-scope', () => { }, ]); }); - it('should annotate rules under "@st-scope"', () => { - const meta = processSource( - ` - @st-scope .root{ - .part {} - } - `, - { from: 'path/to/style.css' } - ); - - shouldReportNoDiagnostics(meta); - const rule = meta.ast.nodes[0] as SRule; - expect(getRuleScopeSelector(rule)).to.equal('.root'); - expect(getRuleScopeSelector(rule.clone()), 'clone rules preserve stScope').to.equal( - '.root' - ); - }); it('should parse "@st-scope" directives with a new class', () => { const meta = processSource( @@ -71,26 +58,6 @@ describe('@st-scope', () => { }, ]); }); - - it('should mark scope ref name on impacted rules', () => { - const meta = processSource( - ` - @st-scope .root { - .part {} - .otherPart {} - } - `, - { from: 'path/to/style.css' } - ); - - const rules = meta.ast.nodes; - - shouldReportNoDiagnostics(meta); - - expect((rules[0] as Rule).selector).to.equal('.root .part'); - expect((rules[1] as Rule).selector).to.equal('.root .otherPart'); - expect(rules[2]).to.eql(undefined); - }); }); describe('transforming scoped selectors', () => { @@ -111,7 +78,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - expect((meta.outputAst!.nodes[0] as Rule).selector).to.equal( + expect((meta.targetAst!.nodes[0] as Rule).selector).to.equal( '.entry__root .entry__part' ); }); @@ -133,7 +100,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - expect((meta.outputAst!.nodes[0] as Rule).selector).to.equal( + expect((meta.targetAst!.nodes[0] as Rule).selector).to.equal( '.entry__scope1 .entry__part1, .entry__scope2 .entry__part1, .entry__scope1 .entry__part2, .entry__scope2 .entry__part2' ); }); @@ -155,7 +122,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - expect((meta.outputAst!.nodes[0] as Rule).selector).to.equal('* .entry__part'); + expect((meta.targetAst!.nodes[0] as Rule).selector).to.equal('* .entry__part'); }); it('should support :global() selector', () => { @@ -175,7 +142,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - expect((meta.outputAst!.nodes[0] as Rule).selector).to.equal('.my-class .entry__part'); + expect((meta.targetAst!.nodes[0] as Rule).selector).to.equal('.my-class .entry__part'); }); it('should selectors with internal parts', () => { @@ -205,7 +172,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - expect((meta.outputAst!.nodes[1] as Rule).selector).to.equal( + expect((meta.targetAst!.nodes[1] as Rule).selector).to.equal( '.entry__root .imported__part .entry__part1, .entry__root .imported__part .entry__part2' ); }); @@ -237,7 +204,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - expect((meta.outputAst!.first as Rule).selector).to.equal( + expect((meta.targetAst!.first as Rule).selector).to.equal( '.imported__importedPart .entry__part' ); }); @@ -262,7 +229,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - expect((meta.outputAst!.nodes[2] as Rule).selector).to.equal( + expect((meta.targetAst!.nodes[2] as Rule).selector).to.equal( '.entry__root .entry__part .entry__scopedPart' ); @@ -292,7 +259,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - expect((meta.outputAst!.nodes[0] as Rule).selector).to.equal( + expect((meta.targetAst!.nodes[0] as Rule).selector).to.equal( '.entry__root .entry__part, .entry__root .entry__otherPart, .entry__root .entry__oneMorePart' ); }); @@ -322,7 +289,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - expect(meta.outputAst!.first).to.flatMatch({ + expect(meta.targetAst!.first).to.flatMatch({ selector: '.imported__root .entry__part', }); }); @@ -352,7 +319,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - expect(meta.outputAst!.first).to.flatMatch({ + expect(meta.targetAst!.first).to.flatMatch({ selector: '.entry__root .imported__root', }); }); @@ -387,7 +354,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - const rule: Rule = meta.outputAst!.first as Rule; + const rule: Rule = meta.targetAst!.first as Rule; const decl: Declaration = rule.first as Declaration; expect(decl).to.equal(undefined); }); @@ -423,7 +390,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - const rule: Rule = meta.outputAst!.nodes[1] as Rule; + const rule: Rule = meta.targetAst!.nodes[1] as Rule; const decl: Declaration = rule.first as Declaration; expect(decl.value).to.equal('red'); }); @@ -461,7 +428,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - const rule = meta.outputAst!.nodes[1] as Rule; + const rule = meta.targetAst!.nodes[1] as Rule; expect(rule.selector).to.equal('.entry__root.imported--myState'); }); @@ -484,7 +451,7 @@ describe('@st-scope', () => { shouldReportNoDiagnostics(meta); - const atRule = meta.outputAst!.nodes[0] as AtRule; + const atRule = meta.targetAst!.nodes[0] as AtRule; const rule = atRule.nodes[0] as Rule; expect(rule.selector).to.equal('.entry__root .entry__part'); }); @@ -508,12 +475,12 @@ describe('@st-scope', () => { const { meta } = expectTransformDiagnostics(config, [ { - message: transformerWarnings.UNKNOWN_PSEUDO_ELEMENT('unknownPart'), + message: transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT('unknownPart'), file: '/entry.st.css', - severity: 'warning', + severity: 'error', }, ]); - expect((meta.outputAst!.first as Rule).selector).to.equal( + expect((meta.targetAst!.first as Rule).selector).to.equal( '.entry__root::unknownPart .entry__part' ); }); @@ -534,18 +501,18 @@ describe('@st-scope', () => { const { meta } = expectTransformDiagnostics(config, [ { - message: transformerWarnings.UNKNOWN_PSEUDO_ELEMENT('unknownPart'), + message: transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT('unknownPart'), file: '/entry.st.css', - severity: 'warning', + severity: 'error', }, { - message: transformerWarnings.UNKNOWN_PSEUDO_ELEMENT('unknownPart'), + message: transformerStringDiagnostics.UNKNOWN_PSEUDO_ELEMENT('unknownPart'), file: '/entry.st.css', - severity: 'warning', + severity: 'error', skipLocationCheck: true, }, ]); - expect((meta.outputAst!.first as Rule).selector).to.equal( + expect((meta.targetAst!.first as Rule).selector).to.equal( '.entry__root::unknownPart .entry__part::unknownPart' ); }); @@ -566,12 +533,12 @@ describe('@st-scope', () => { const { meta } = expectTransformDiagnostics(config, [ { - message: STScope.diagnostics.MISSING_SCOPING_PARAM(), + message: stScopeDiagnostics.MISSING_SCOPING_PARAM(), file: '/entry.st.css', - severity: 'warning', + severity: 'error', }, ]); - expect((meta.outputAst!.first as Rule).selector).to.equal('.entry__part'); + expect((meta.targetAst!.first as Rule).selector).to.equal('.entry__part'); }); }); }); diff --git a/packages/core/test/stylable-assets.spec.ts b/packages/core/test/stylable-assets.spec.ts index 4848f1dd9..3d4447785 100644 --- a/packages/core/test/stylable-assets.spec.ts +++ b/packages/core/test/stylable-assets.spec.ts @@ -1,6 +1,25 @@ import { normalize } from 'path'; import { expect } from 'chai'; -import { collectAssets, fixRelativeUrls, isAsset, makeAbsolute, cssParse } from '@stylable/core'; +import { cssParse } from '@stylable/core/dist/index-internal'; +import { processDeclarationFunctions } from '@stylable/core/dist/process-declaration-functions'; +import { fixRelativeUrls, isAsset, makeAbsolute } from '@stylable/core/dist/stylable-assets'; +import type * as postcss from 'postcss'; + +function collectAssets(ast: postcss.Root) { + const assetDependencies: string[] = []; + ast.walkDecls((decl) => { + processDeclarationFunctions( + decl, + (node) => { + if (node.type === 'url') { + assetDependencies.push(node.url); + } + }, + false + ); + }); + return assetDependencies; +} const css = ` .a{ diff --git a/packages/core/test/stylable-processor.spec.ts b/packages/core/test/stylable-processor.spec.ts index a11d3c4ac..0cb439457 100644 --- a/packages/core/test/stylable-processor.spec.ts +++ b/packages/core/test/stylable-processor.spec.ts @@ -1,17 +1,22 @@ import { resolve } from 'path'; import chai, { expect } from 'chai'; -import { flatMatch, processSource } from '@stylable/core-test-kit'; -import { processNamespace, processorWarnings } from '@stylable/core'; -import { knownPseudoClassesWithNestedSelectors } from '@stylable/core/dist/native-reserved-lists'; +import { flatMatch, processSource, diagnosticBankReportToStrings } from '@stylable/core-test-kit'; +import { processNamespace } from '@stylable/core'; +import { + knownPseudoClassesWithNestedSelectors, + processorDiagnostics, +} from '@stylable/core/dist/index-internal'; chai.use(flatMatch); +const processorStringDiagnostics = diagnosticBankReportToStrings(processorDiagnostics); + describe('Stylable postcss process', () => { it('report if missing filename', () => { const { diagnostics, namespace } = processSource(``); expect(namespace).to.equal('s0'); expect(diagnostics.reports[0]).to.include({ - type: 'error', + severity: 'error', message: 'missing source filename', }); }); @@ -20,8 +25,8 @@ describe('Stylable postcss process', () => { const { diagnostics } = processSource(`@namespace App;`, { from: '/path/to/source' }); expect(diagnostics.reports[0]).to.include({ - type: 'error', - message: processorWarnings.INVALID_NAMESPACE_DEF(), + severity: 'error', + message: processorStringDiagnostics.INVALID_NAMESPACE_DEF(), }); }); @@ -29,8 +34,8 @@ describe('Stylable postcss process', () => { const { diagnostics } = processSource(`@namespace ' ';`, { from: '/path/to/source' }); expect(diagnostics.reports[0]).to.include({ - type: 'error', - message: processorWarnings.EMPTY_NAMESPACE_DEF(), + severity: 'error', + message: processorStringDiagnostics.EMPTY_NAMESPACE_DEF(), }); }); @@ -46,8 +51,8 @@ describe('Stylable postcss process', () => { ); expect(diagnostics.reports[0]).to.include({ - type: 'error', - message: processorWarnings.INVALID_NESTING('.y', '.x'), + severity: 'error', + message: processorStringDiagnostics.INVALID_NESTING('.y', '.x'), }); }); diff --git a/packages/core/test/stylable-resolver.spec.ts b/packages/core/test/stylable-resolver.spec.ts index 8ab0c8b12..5a206429a 100644 --- a/packages/core/test/stylable-resolver.spec.ts +++ b/packages/core/test/stylable-resolver.spec.ts @@ -1,29 +1,26 @@ import { expect } from 'chai'; import type * as postcss from 'postcss'; import { testStylableCore, generateStylableResult } from '@stylable/core-test-kit'; -import { - cssParse, - StylableResolver, - cachedProcessFile, - MinimalFS, - StylableMeta, - createDefaultResolver, -} from '@stylable/core'; -import { process } from '@stylable/core/dist/index-internal'; +import { Stylable, MinimalFS, StylableMeta, createDefaultResolver } from '@stylable/core'; +import { StylableResolver, cachedProcessFile } from '@stylable/core/dist/index-internal'; function createResolveExtendsResults( - fs: MinimalFS, + fileSystem: MinimalFS, fileToProcess: string, classNameToLookup: string, isElement = false ) { - const moduleResolver = createDefaultResolver(fs, {}); + const moduleResolver = createDefaultResolver(fileSystem, {}); + const stylable = new Stylable({ + fileSystem, + projectRoot: '/', + }); const processFile = cachedProcessFile( - (fullpath, content) => { - return process(cssParse(content, { from: fullpath })); + (fullPath, content) => { + return stylable.analyze(fullPath, content); }, - (filePath: string) => fs.readFileSync(filePath, 'utf8') + (filePath: string) => fileSystem.readFileSync(filePath, 'utf8') ); const resolver = new StylableResolver( @@ -402,7 +399,7 @@ describe('stylable-resolver', () => { }, }); - const rule = meta.outputAst!.nodes[0] as postcss.Rule; + const rule = meta.targetAst!.nodes[0] as postcss.Rule; expect(rule.selector).to.equal('.A__root'); expect(meta.diagnostics.reports).to.eql([]); expect(meta.transformDiagnostics!.reports).to.eql([]); diff --git a/packages/core/test/stylable-transformer/global.spec.ts b/packages/core/test/stylable-transformer/global.spec.ts index fc7ea5af6..734cf4d7a 100644 --- a/packages/core/test/stylable-transformer/global.spec.ts +++ b/packages/core/test/stylable-transformer/global.spec.ts @@ -37,9 +37,9 @@ describe('Stylable postcss transform (Global)', () => { d: true, e: true, }); - expect((meta.outputAst!.nodes[1] as postcss.Rule).selector).to.equal('.global-test'); - expect((meta.outputAst!.nodes[2] as postcss.Rule).selector).to.equal('.a .b'); - expect((meta.outputAst!.nodes[3] as postcss.Rule).selector).to.equal('.c .d'); - expect((meta.outputAst!.nodes[4] as postcss.Rule).selector).to.equal('.e'); + expect((meta.targetAst!.nodes[1] as postcss.Rule).selector).to.equal('.global-test'); + expect((meta.targetAst!.nodes[2] as postcss.Rule).selector).to.equal('.a .b'); + expect((meta.targetAst!.nodes[3] as postcss.Rule).selector).to.equal('.c .d'); + expect((meta.targetAst!.nodes[4] as postcss.Rule).selector).to.equal('.e'); }); }); diff --git a/packages/core/test/stylable-transformer/post-process.spec.ts b/packages/core/test/stylable-transformer/post-process.spec.ts index 3ab590b2a..ad05ab573 100644 --- a/packages/core/test/stylable-transformer/post-process.spec.ts +++ b/packages/core/test/stylable-transformer/post-process.spec.ts @@ -7,7 +7,7 @@ describe('post-process', () => { stylableConfig: { hooks: { postProcessor(res) { - res.meta.outputAst!.walkRules((rule) => { + res.meta.targetAst!.walkRules((rule) => { rule.selector = rule.selector.replace(`.entry__part`, `.custom-part`); }); res.exports.classes.part = `custom-part`; @@ -18,7 +18,7 @@ describe('post-process', () => { }); const { meta, exports } = sheets[`/entry.st.css`]; - expect(meta.outputAst?.toString(), `change meta`).to.eql(`.custom-part {}`); + expect(meta.targetAst?.toString(), `change meta`).to.eql(`.custom-part {}`); expect(exports.classes.part, `JS exports`).to.eql(`custom-part`); }); }); diff --git a/packages/core/test/stylable-transformer/scoping.spec.ts b/packages/core/test/stylable-transformer/scoping.spec.ts index 662178420..41c7d842e 100644 --- a/packages/core/test/stylable-transformer/scoping.spec.ts +++ b/packages/core/test/stylable-transformer/scoping.spec.ts @@ -72,7 +72,7 @@ describe('Stylable postcss transform (Scoping)', () => { .root:not(::nestedPart) {} /* @rule(custom-selector syntax) - .entry__root:not(.entry__partA), .entry__root:not(.entry__partB) + .entry__root:not(.entry__partA),.entry__root:not(.entry__partB) */ .root:not(:--part) {} `); diff --git a/packages/core/test/stylable-utils.spec.ts b/packages/core/test/stylable-utils.spec.ts index 74c1322dc..734f75fe2 100644 --- a/packages/core/test/stylable-utils.spec.ts +++ b/packages/core/test/stylable-utils.spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { visitMetaCSSDependenciesBFS } from '@stylable/core'; +import { visitMetaCSSDependencies } from '@stylable/core/dist/visit-meta-css-dependencies'; import { testStylableCore } from '@stylable/core-test-kit'; describe('visitMetaCSSDependenciesBFS', () => { @@ -56,19 +56,16 @@ describe('visitMetaCSSDependenciesBFS', () => { `, }); + const resolver = stylable.resolver; const entryMeta = stylable.fileProcessor.process('/entry.st.css'); const items: { source: string; depth: number }[] = []; - visitMetaCSSDependenciesBFS( - entryMeta, - ({ source }, _, depth) => { - items.push({ source, depth }); - }, - stylable.resolver, - (source) => { - items.push({ source, depth: -1 }); - } - ); + for (const dep of visitMetaCSSDependencies({ meta: entryMeta, resolver })) { + items.push({ + depth: dep.kind === 'css' ? dep.depth : -1, + source: dep.resolvedPath, + }); + } expect(items).to.eql([ { source: '/d1.st.css', depth: 1 }, diff --git a/packages/core/test/stylable.spec.ts b/packages/core/test/stylable.spec.ts index 0c48dd034..9b24cf854 100644 --- a/packages/core/test/stylable.spec.ts +++ b/packages/core/test/stylable.spec.ts @@ -1,5 +1,6 @@ import { testStylableCore, generateStylableEnvironment } from '@stylable/core-test-kit'; import { expect } from 'chai'; +import deindent from 'deindent'; describe('Stylable', () => { describe(`analyze (generate meta) `, () => { @@ -30,12 +31,12 @@ describe('Stylable', () => { const overrideMeta = stylable.analyze(path, overrideContent); const fsMetaAfter = stylable.analyze(path); - expect(overrideMeta.ast.toString(), `override src ast`).to.eql(`.override {}`); + expect(overrideMeta.sourceAst.toString(), `override src ast`).to.eql(`.override {}`); expect(overrideMeta, `override meta`).to.contain({ source: path, namespace: `entry`, }); - expect(fsMetaBefore.ast.toString(), `fs before src ast`).to.eql(`.fs {}`); + expect(fsMetaBefore.sourceAst.toString(), `fs before src ast`).to.eql(`.fs {}`); expect(fsMetaBefore, `fs meta`).to.contain({ source: path, namespace: `entry`, @@ -44,26 +45,54 @@ describe('Stylable', () => { }); }); describe(`transformation`, () => { - it(`should transform a stylesheet by content & path`, () => { - const src = `.a {}`; - const path = `/entry.st.css`; - const { stylable } = testStylableCore({}); - - const { meta, exports } = stylable.transform(src, path); - - expect(meta.outputAst?.toString(), `output CSS`).to.eql(`.entry__a {}`); - expect(exports.classes.a, `JS export`).to.eql(`entry__a`); - }); it(`should transform a stylesheet from meta`, () => { - const src = `.a {}`; + const src = ` + :vars { + varA: red; + } + .a { + prop: value(varA); + } + `; const path = `/entry.st.css`; const { stylable, fs } = testStylableCore({}); fs.writeFileSync(path, src); + const meta = stylable.analyze(path); - const { meta, exports } = stylable.transform(stylable.analyze(path)); + const defaultTransform = stylable.transform(meta); - expect(meta.outputAst?.toString(), `output CSS`).to.eql(`.entry__a {}`); - expect(exports.classes.a, `JS export`).to.eql(`entry__a`); + expect( + deindent(defaultTransform.meta.targetAst!.toString()), + `default output CSS` + ).to.eql( + deindent(` + .entry__a { + prop: red; + } + `) + ); + expect(defaultTransform.exports.classes.a, `JS export`).to.eql(`entry__a`); + expect(defaultTransform.exports.stVars.varA, `default var JS export`).to.eql(`red`); + + // test with override + const varOverrideTransform = stylable.transform(meta, { + stVarOverride: { varA: 'green' }, + }); + + expect( + deindent(varOverrideTransform.meta.targetAst!.toString()), + `override vars output CSS` + ).to.eql( + deindent(` + .entry__a { + prop: green; + } + `) + ); + // ToDo: fix JS export for programmatic override + // expect(varOverrideTransform.exports.stVars.varA, `override var JS export`).to.eql( + // `green` + // ); }); it(`should transform selector`, () => { const path = `/entry.st.css`; @@ -132,6 +161,29 @@ describe('Stylable', () => { value: `var(--entry-x) jump`, }); }); + it(`should transform declaration prop/value with override st-vars`, () => { + const path = `/entry.st.css`; + const { stylable } = testStylableCore({ + [path]: ` + :vars { + x: red; + a: value(x); + b: blue; + } + `, + }); + + const decl = stylable.transformDecl(path, `prop`, `value(a) value(b) value(x)`, { + stVarOverride: { + x: 'green', + }, + }); + + expect(decl, `value override`).to.eql({ + prop: `prop`, + value: `green blue green`, + }); + }); it(`should transform custom property`, () => { const path = `/entry.st.css`; const { stylable } = testStylableCore({ diff --git a/packages/create-stylable-app/package.json b/packages/create-stylable-app/package.json index 50b154636..68f6e68cc 100644 --- a/packages/create-stylable-app/package.json +++ b/packages/create-stylable-app/package.json @@ -1,6 +1,6 @@ { "name": "create-stylable-app", - "version": "4.14.0", + "version": "5.0.0", "description": "Quickly create a Stylable application", "main": "dist/index.js", "bin": { @@ -27,7 +27,7 @@ "application" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/custom-value/package.json b/packages/custom-value/package.json index 01d2e74db..7ffc61266 100644 --- a/packages/custom-value/package.json +++ b/packages/custom-value/package.json @@ -1,13 +1,13 @@ { "name": "@stylable/custom-value", - "version": "4.14.0", + "version": "5.0.0", "description": "Custom values (variables) for Stylable", "main": "dist/index.js", "scripts": { "test": "mocha \"./dist/test/**/*.spec.js\" --timeout 20000" }, "dependencies": { - "@stylable/core": "^4.14.0" + "@stylable/core": "^5.0.0" }, "files": [ "dist", @@ -16,7 +16,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/custom-value/src/st-border.ts b/packages/custom-value/src/st-border.ts index 835007cbb..fd6135548 100644 --- a/packages/custom-value/src/st-border.ts +++ b/packages/custom-value/src/st-border.ts @@ -1,9 +1,9 @@ +import { CustomValueStrategy } from '@stylable/core'; import { BoxedValueArray, BoxedValueMap, createCustomValue, - CustomValueStrategy, -} from '@stylable/core'; +} from '@stylable/core/dist/index-internal'; export const stBorder = createCustomValue({ processArgs: (node, customTypes) => { diff --git a/packages/dom-test-kit/package.json b/packages/dom-test-kit/package.json index 2b2250153..4c5e7d30a 100644 --- a/packages/dom-test-kit/package.json +++ b/packages/dom-test-kit/package.json @@ -1,6 +1,6 @@ { "name": "@stylable/dom-test-kit", - "version": "4.14.0", + "version": "5.0.0", "description": "Stylable DOM testing utilities", "main": "dist/index.js", "scripts": { @@ -8,8 +8,8 @@ "start": "mocha-play \"./dist/test/**/*.spec.js\" -c ./webpack.config.js -w" }, "dependencies": { - "@stylable/core": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/runtime": "^5.0.0", "@tokey/css-selector-parser": "^0.6.0" }, "files": [ @@ -19,7 +19,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/dom-test-kit/src/stylable-dom-util.ts b/packages/dom-test-kit/src/stylable-dom-util.ts index 28c77ecf5..d2da57de7 100644 --- a/packages/dom-test-kit/src/stylable-dom-util.ts +++ b/packages/dom-test-kit/src/stylable-dom-util.ts @@ -1,4 +1,4 @@ -import { pseudoStates } from '@stylable/core'; +import { pseudoStates } from '@stylable/core/dist/index-internal'; import { parseCssSelector, walk, diff --git a/packages/e2e-test-kit/package.json b/packages/e2e-test-kit/package.json index 423a559f3..03327ccd8 100644 --- a/packages/e2e-test-kit/package.json +++ b/packages/e2e-test-kit/package.json @@ -1,13 +1,13 @@ { "name": "@stylable/e2e-test-kit", - "version": "4.14.0", + "version": "5.0.0", "description": "A collection of tools to help test Stylable components and applications from end-to-end", "main": "dist/index.js", "peerDependencies": { "webpack": "^5.30.0" }, "dependencies": { - "@stylable/runtime": "^4.14.0", + "@stylable/runtime": "^5.0.0", "express": "^4.18.1", "node-eval": "^2.0.0", "playwright-core": "^1.23.2", @@ -20,7 +20,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/e2e-test-kit/src/dts-kit.ts b/packages/e2e-test-kit/src/dts-kit.ts index b206222fc..7e8a4610a 100644 --- a/packages/e2e-test-kit/src/dts-kit.ts +++ b/packages/e2e-test-kit/src/dts-kit.ts @@ -21,7 +21,7 @@ export class DTSKit { }; this.tmp = createTempDirectorySync('dts-gen'); - this.stylable = Stylable.create({ + this.stylable = new Stylable({ projectRoot: this.tmp.path, fileSystem: fs, resolveNamespace(ns) { diff --git a/packages/eslint-plugin-stylable/package.json b/packages/eslint-plugin-stylable/package.json index a0231a15b..e444b800a 100644 --- a/packages/eslint-plugin-stylable/package.json +++ b/packages/eslint-plugin-stylable/package.json @@ -1,13 +1,13 @@ { "name": "eslint-plugin-stylable", - "version": "4.14.0", + "version": "5.0.0", "description": "eslint plugin for Stylable usages", "main": "dist/index.js", "scripts": { "test": "mocha \"dist/test/**/*.spec.js\" --timeout 5000" }, "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "@typescript-eslint/experimental-utils": "^5.30.5" }, "keywords": [ diff --git a/packages/eslint-plugin-stylable/src/stylable-es-lint.ts b/packages/eslint-plugin-stylable/src/stylable-es-lint.ts index af67e82cc..c9f0656be 100644 --- a/packages/eslint-plugin-stylable/src/stylable-es-lint.ts +++ b/packages/eslint-plugin-stylable/src/stylable-es-lint.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import path from 'path'; -import { Stylable, createDefaultResolver, StylableExports, StylableMeta } from '@stylable/core'; -import { safeParse } from '@stylable/core/dist/index-internal'; +import { Stylable, StylableMeta, createDefaultResolver } from '@stylable/core'; +import { safeParse, StylableExports } from '@stylable/core/dist/index-internal'; import { ESLintUtils, AST_NODE_TYPES, @@ -24,7 +24,7 @@ export default createRule({ const [{ exposeDiagnosticsReports, resolveOptions }] = options as Options; const moduleResolver = createDefaultResolver(fs, resolveOptions); - const stylable = Stylable.create({ + const stylable = new Stylable({ fileSystem: fs, projectRoot: process.cwd(), resolveModule: moduleResolver, diff --git a/packages/experimental-loader/package.json b/packages/experimental-loader/package.json index 3824c3325..34fb2f9bb 100644 --- a/packages/experimental-loader/package.json +++ b/packages/experimental-loader/package.json @@ -1,14 +1,14 @@ { "name": "@stylable/experimental-loader", - "version": "4.14.0", + "version": "5.0.0", "main": "dist/index.js", "scripts": { "test": "mocha \"./dist/test/**/*.spec.js\" --timeout 20000" }, "dependencies": { - "@stylable/core": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@stylable/runtime": "^5.0.0", "css-loader": "^6.7.1", "decache": "^4.6.1", "loader-utils": "^3.2.0" diff --git a/packages/experimental-loader/src/add-build-info.ts b/packages/experimental-loader/src/add-build-info.ts index 7fd6a52bf..65bde7a3b 100644 --- a/packages/experimental-loader/src/add-build-info.ts +++ b/packages/experimental-loader/src/add-build-info.ts @@ -6,7 +6,9 @@ export function addBuildInfo(ctx: LoaderContext, namespace: string) { } catch (error) { ctx.emitWarning( new Error( - `Failed to add stylableNamespace buildInfo for: ${ctx.resourcePath} because ${(error as Error).message}` + `Failed to add stylableNamespace buildInfo for: ${ctx.resourcePath} because ${ + (error as Error).message + }` ) ); } diff --git a/packages/experimental-loader/src/cached-stylable-factory.ts b/packages/experimental-loader/src/cached-stylable-factory.ts index 04de102e4..e29ef9442 100644 --- a/packages/experimental-loader/src/cached-stylable-factory.ts +++ b/packages/experimental-loader/src/cached-stylable-factory.ts @@ -20,7 +20,7 @@ export function getStylable(compiler: Compiler, initialConfig: StylableConfig): return require(id); }; - stylable = Stylable.create({ ...initialConfig, requireModule, resolverCache: new Map() }); + stylable = new Stylable({ ...initialConfig, requireModule, resolverCache: new Map() }); compiler.hooks.done.tap('StylableLoader stylable.initCache', () => { stylable!.initCache(); for (const id of requireModuleCache) { diff --git a/packages/experimental-loader/src/create-runtime-target-code.ts b/packages/experimental-loader/src/create-runtime-target-code.ts index 7c761dd17..b98fb44dc 100644 --- a/packages/experimental-loader/src/create-runtime-target-code.ts +++ b/packages/experimental-loader/src/create-runtime-target-code.ts @@ -1,4 +1,4 @@ -import type { StylableExports } from '@stylable/core'; +import type { StylableExports } from '@stylable/core/dist/index-internal'; export function createRuntimeTargetCode(namespace: string, mapping: StylableExports) { return ` diff --git a/packages/experimental-loader/src/stylable-runtime-loader.ts b/packages/experimental-loader/src/stylable-runtime-loader.ts index 5f1305cc3..fd33abb2f 100644 --- a/packages/experimental-loader/src/stylable-runtime-loader.ts +++ b/packages/experimental-loader/src/stylable-runtime-loader.ts @@ -1,5 +1,5 @@ import type { LoaderDefinition } from 'webpack'; -import type { StylableExports } from '@stylable/core'; +import type { StylableExports } from '@stylable/core/dist/index-internal'; import { createRuntimeTargetCode } from './create-runtime-target-code'; import { addBuildInfo } from './add-build-info'; diff --git a/packages/experimental-loader/src/stylable-transform-loader.ts b/packages/experimental-loader/src/stylable-transform-loader.ts index 5fb03c47d..2434e49a1 100644 --- a/packages/experimental-loader/src/stylable-transform-loader.ts +++ b/packages/experimental-loader/src/stylable-transform-loader.ts @@ -1,6 +1,10 @@ import postcss from 'postcss'; -import { processNamespace, emitDiagnostics, DiagnosticsMode, MinimalFS } from '@stylable/core'; -import { tryCollectImportsDeep } from '@stylable/core/dist/index-internal'; +import { processNamespace, MinimalFS } from '@stylable/core'; +import { + emitDiagnostics, + DiagnosticsMode, + tryCollectImportsDeep, +} from '@stylable/core/dist/index-internal'; import { StylableOptimizer } from '@stylable/optimizer'; import { Warning, CssSyntaxError } from './warning'; import { getStylable } from './cached-stylable-factory'; @@ -71,7 +75,7 @@ const stylableLoader: LoaderDefinition = function (content) { resolveNamespace, }); - const { meta, exports } = stylable.transform(content, this.resourcePath); + const { meta, exports } = stylable.transform(stylable.analyze(this.resourcePath, content)); emitDiagnostics(this, meta, diagnosticsMode); for (const filePath of tryCollectImportsDeep(stylable, meta)) { @@ -121,11 +125,11 @@ const stylableLoader: LoaderDefinition = function (content) { ]; if (mode !== 'development') { - optimizer.removeStylableDirectives(meta.outputAst!); + optimizer.removeStylableDirectives(meta.targetAst!); } postcss(plugins) - .process(meta.outputAst!, { + .process(meta.targetAst!, { from: this.resourcePath, to: this.resourcePath, map: false, diff --git a/packages/experimental-loader/test/errors-integration.spec.ts b/packages/experimental-loader/test/errors-integration.spec.ts index f35476f63..e30c4e541 100644 --- a/packages/experimental-loader/test/errors-integration.spec.ts +++ b/packages/experimental-loader/test/errors-integration.spec.ts @@ -1,7 +1,13 @@ import { StylableProjectRunner } from '@stylable/e2e-test-kit'; +import { parseImportMessages } from '@stylable/core/dist/helpers/import'; +import { diagnostics as cssTypeDiagnostics } from '@stylable/core/dist/features/css-type'; +import { diagnosticBankReportToStrings } from '@stylable/core-test-kit'; import { expect } from 'chai'; import { dirname } from 'path'; +const typeDiagnostics = diagnosticBankReportToStrings(cssTypeDiagnostics); +const parseImportDiagnostics = diagnosticBankReportToStrings(parseImportMessages); + const project = 'errors-integration'; const projectDir = dirname( require.resolve(`@stylable/experimental-loader/test/projects/${project}/webpack.config`) @@ -24,7 +30,7 @@ describe(`(${project})`, () => { it('emit errors and warnings from loader', () => { const errors = projectRunner.getBuildErrorMessagesDeep(); const warnings = projectRunner.getBuildWarningsMessagesDeep(); - expect(errors[0].message).to.include(`"-st-from" cannot be empty`); - expect(warnings[0].message).to.include(`cannot resolve '-st-extends' type for 'Unknown'`); + expect(errors[0].message).to.include(parseImportDiagnostics.EMPTY_IMPORT_FROM()); + expect(warnings[0].message).to.include(typeDiagnostics.UNSCOPED_TYPE_SELECTOR('Unknown')); }); }); diff --git a/packages/experimental-loader/test/projects/errors-integration/index.st.css b/packages/experimental-loader/test/projects/errors-integration/index.st.css index 0277d82b4..9655c9119 100644 --- a/packages/experimental-loader/test/projects/errors-integration/index.st.css +++ b/packages/experimental-loader/test/projects/errors-integration/index.st.css @@ -3,6 +3,6 @@ -st-default: NotFound; } -.root { - -st-extends: Unknown; -} \ No newline at end of file +Unknown { + color: red; +} diff --git a/packages/experimental-loader/test/tsconfig.json b/packages/experimental-loader/test/tsconfig.json index 747cdbdef..31a2df100 100644 --- a/packages/experimental-loader/test/tsconfig.json +++ b/packages/experimental-loader/test/tsconfig.json @@ -5,6 +5,7 @@ "types": ["node", "externals", "mocha"] }, "references": [ + { "path": "../../core-test-kit/src" }, { "path": "../../e2e-test-kit/src" }, { "path": "../../core/src" }, { "path": "../src" } diff --git a/packages/jest/package.json b/packages/jest/package.json index 4f2882880..f69922c57 100644 --- a/packages/jest/package.json +++ b/packages/jest/package.json @@ -1,16 +1,16 @@ { "name": "@stylable/jest", - "version": "4.14.0", + "version": "5.0.0", "description": "Test your Stylable React components using Jest", "main": "dist/index.js", "scripts": { "test": "mocha \"./dist/test/**/*.spec.js\"" }, "dependencies": { - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/runtime": "^4.14.0" + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/runtime": "^5.0.0" }, "files": [ "dist", @@ -19,7 +19,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/language-service/package.json b/packages/language-service/package.json index 99544e1ce..961244ca6 100644 --- a/packages/language-service/package.json +++ b/packages/language-service/package.json @@ -1,6 +1,6 @@ { "name": "@stylable/language-service", - "version": "4.14.0", + "version": "5.0.0", "description": "Stylable Language Services - syntax highlighting, completions, hinting and more for the Stylable CSS preprocessor.", "main": "dist/index.js", "scripts": { @@ -13,13 +13,13 @@ "dependencies": { "@file-services/types": "^6.0.0", "@file-services/typescript": "^6.0.0", - "@stylable/code-formatter": "^4.14.0", - "@stylable/core": "^4.14.0", + "@stylable/code-formatter": "^5.0.0", + "@stylable/core": "^5.0.0", "css-selector-tokenizer": "^0.8.0", "postcss": "^8.4.14", "postcss-value-parser": "^4.2.0", - "vscode-css-languageservice": "^5.4.2", - "vscode-languageserver": "^7.0.0", + "vscode-css-languageservice": "^6.0.1", + "vscode-languageserver": "^8.0.1", "vscode-languageserver-textdocument": "^1.0.5", "vscode-uri": "^3.0.3" }, @@ -31,7 +31,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/language-service/src/lib/completion-providers.ts b/packages/language-service/src/lib/completion-providers.ts index f98bce219..e4f90252d 100644 --- a/packages/language-service/src/lib/completion-providers.ts +++ b/packages/language-service/src/lib/completion-providers.ts @@ -3,21 +3,23 @@ import type * as postcss from 'postcss'; import postcssValueParser from 'postcss-value-parser'; import type ts from 'typescript'; -import { +import type { ClassSymbol, CSSResolve, CSSVarSymbol, ElementSymbol, - nativePseudoClasses, - nativePseudoElements, - ResolvedElement, - SRule, Stylable, StylableMeta, - systemValidators, VarSymbol, } from '@stylable/core'; -import type { MappedStates } from '@stylable/core/dist/index-internal'; +import { + MappedStates, + nativePseudoClasses, + nativePseudoElements, + ResolvedElement, + STCustomSelector, + systemValidators, +} from '@stylable/core/dist/index-internal'; import type { IFileSystem } from '@file-services/types'; import { classCompletion, @@ -63,7 +65,7 @@ export interface ProviderOptions { tsLangService: ExtendedTsLanguageService; // candidate for removal resolvedElements: ResolvedElement[][]; // candidate for removal resolvedRoot: ResolvedElement; // candidate for removal - parentSelector: SRule | null; + parentSelector: postcss.Rule | null; astAtCursor: postcss.AnyNode; // candidate for removal lineChunkAtCursor: string; lastSelectoid: string; // candidate for removal @@ -312,8 +314,9 @@ export const TopLevelDirectiveProvider: CompletionProvider = { return topLevelDeclarations .filter( (d) => - !meta.ast.source!.input.css.includes(topLevelDirectives.namespace) || - d !== 'namespace' + !meta.sourceAst.source!.input.css.includes( + topLevelDirectives.namespace + ) || d !== 'namespace' ) .filter((d) => topLevelDirectives[d].startsWith(fullLineText.trim())) .map((d) => @@ -472,7 +475,7 @@ export const SelectorCompletionProvider: CompletionProvider = { ) ); comps.push( - ...Object.keys(meta.customSelectors).map((c) => + ...STCustomSelector.getCustomSelectorNames(meta).map((c) => classCompletion( c, createDirectiveRange(position, fullLineText, lineChunkAtCursor), @@ -1018,7 +1021,9 @@ export const PseudoElementCompletionProvider: CompletionProvider = { comps = comps.concat( Object.keys(res.meta.getAllClasses()) .concat( - Object.keys(res.meta.customSelectors).map((s) => s.slice(':--'.length)) + STCustomSelector.getCustomSelectorNames(res.meta).map((s) => + s.slice(':--'.length) + ) ) .filter((e) => e.startsWith(filter) && e !== 'root') .map((c) => { @@ -1061,7 +1066,7 @@ export const PseudoElementCompletionProvider: CompletionProvider = { comps = comps.concat( Object.keys(res.meta.getAllClasses()) .concat( - Object.keys(res.meta.customSelectors).map((s) => + STCustomSelector.getCustomSelectorNames(res.meta).map((s) => s.slice(':--'.length) ) ) diff --git a/packages/language-service/src/lib/diagnosis.ts b/packages/language-service/src/lib/diagnosis.ts index 88f8dcd43..383b4c241 100644 --- a/packages/language-service/src/lib/diagnosis.ts +++ b/packages/language-service/src/lib/diagnosis.ts @@ -21,7 +21,7 @@ export function createDiagnosis( /*TODO: report this failure to transform */ } - const cleanDoc = cssService.createSanitizedDocument(meta.rawAst, filePath, version); + const cleanDoc = cssService.createSanitizedDocument(meta.sourceAst, filePath, version); return meta.diagnostics.reports .concat(meta.transformDiagnostics ? meta.transformDiagnostics.reports : []) @@ -30,7 +30,7 @@ export function createDiagnosis( // stylable diagnostic to protocol diagnostic function reportToDiagnostic(report: StylableDiagnostic) { - const severity = report.type === 'error' ? 1 : 2; + const severity = report.severity === 'error' ? 1 : 2; const range = createRange(report); // todo: incorporate diagnostics code in v5 @@ -42,17 +42,17 @@ export function createRange(report: StylableDiagnostic) { const source = report.node.source; const start = { line: 0, character: 0 }; const end = { line: 0, character: 0 }; - if (report.options.word && source) { + if (report.word && source) { const lines: string[] = (source.input as any).css.split('\n'); const searchStart = source.start!.line - 1; const searchEnd = source.end!.line - 1; for (let i = searchStart; i <= searchEnd; ++i) { - const wordIndex = lines[i].indexOf(report.options.word); + const wordIndex = lines[i].indexOf(report.word); if (~wordIndex) { start.line = i; start.character = wordIndex; end.line = i; - end.character = wordIndex + report.options.word.length; + end.character = wordIndex + report.word.length; break; } } diff --git a/packages/language-service/src/lib/feature/color-provider.ts b/packages/language-service/src/lib/feature/color-provider.ts index 56fc6438b..338358daf 100644 --- a/packages/language-service/src/lib/feature/color-provider.ts +++ b/packages/language-service/src/lib/feature/color-provider.ts @@ -50,7 +50,9 @@ export function resolveDocumentColors( const impMeta = processor.process( stylable.resolvePath(dirname(meta.source), sym.import.request) ); - const relevantVar = Object.values(impMeta.getAllStVars()).find((v) => v.name === sym.name); + const relevantVar = Object.values(impMeta.getAllStVars()).find( + (v) => v.name === sym.name + ); if (relevantVar) { const doc = TextDocument.create( '', @@ -91,7 +93,7 @@ export function resolveDocumentColors( }); const cleanDocument = cssService.createSanitizedDocument( - meta.rawAst, + meta.sourceAst, filePath, document.version ); @@ -116,7 +118,7 @@ export function getColorPresentation( params.range.start.character + 1 ); let noPicker = false; - meta?.rawAst.walkDecls(`-st-named`, (node) => { + meta?.sourceAst.walkDecls(`-st-named`, (node) => { if ( node && ((wordStart.line === node.source!.start!.line && diff --git a/packages/language-service/src/lib/feature/pseudo-class.ts b/packages/language-service/src/lib/feature/pseudo-class.ts index 0721afa45..00a725b0c 100644 --- a/packages/language-service/src/lib/feature/pseudo-class.ts +++ b/packages/language-service/src/lib/feature/pseudo-class.ts @@ -4,14 +4,14 @@ import type { SignatureHelp, SignatureInformation, } from 'vscode-languageserver'; -import { StateParsedValue, systemValidators } from '@stylable/core'; +import { StateParsedValue, systemValidators } from '@stylable/core/dist/index-internal'; import type { ProviderPosition } from '../completion-providers'; // Goes over an '-st-states' declaration value // parses the state and position to resolve if inside a state with a parameter // returns: `-st-states: someState([1] str[2]ing([a] re[b]gex( [args] ) [c]) [3]) // caret positions: -// 1, 2, 3 - requires typing information (string, number, enum, tag) +// 1, 2, 3 - requires typing information (string, number, enum) // a, b, c - require validator informationbased on type defined // args - TODO: should return validator function information export function resolveStateTypeOrValidator( diff --git a/packages/language-service/src/lib/provider.ts b/packages/language-service/src/lib/provider.ts index 7b4b80292..11ccbaad1 100644 --- a/packages/language-service/src/lib/provider.ts +++ b/packages/language-service/src/lib/provider.ts @@ -7,15 +7,18 @@ import type { IFileSystem, IFileSystemDescriptor } from '@file-services/types'; import { ClassSymbol, CSSResolve, - expandCustomSelectors, ImportSymbol, - SRule, - StateParsedValue, Stylable, StylableMeta, JSResolve, + Diagnostics, } from '@stylable/core'; -import { safeParse, process as stylableProcess } from '@stylable/core/dist/index-internal'; +import { + safeParse, + StylableProcessor, + STCustomSelector, + StateParsedValue, +} from '@stylable/core/dist/index-internal'; import type { Location, ParameterInformation, @@ -237,7 +240,7 @@ export class Provider { position.line + 1, position.character ); - const pfp = pathFromPosition(callingMeta.rawAst, postcsspos, [], true); + const pfp = pathFromPosition(callingMeta.sourceAst, postcsspos, [], true); const selec = (pfp[pfp.length - 1] as postcss.Rule).selector; // If called from -st-state, i.e. inside node, pos is not in selector. @@ -291,7 +294,7 @@ export class Provider { ) ); } - } else if (Object.keys(meta.customSelectors).find((sym) => sym === ':--' + word)) { + } else if (STCustomSelector.getCustomSelector(meta, word)) { defs.push( new ProviderLocation(meta.source, this.findWord(':--' + word, src, position)) ); @@ -355,7 +358,7 @@ export class Provider { const line = split[pos.line]; let value = ''; - const stPath = pathFromPosition(meta.rawAst, { + const stPath = pathFromPosition(meta.sourceAst, { line: pos.line + 1, character: pos.character + 1, }); @@ -724,7 +727,7 @@ export class Provider { cursorPosInLine: number, fs: IFileSystem ): ProviderOptions { - const path = pathFromPosition(meta.rawAst, { + const path = pathFromPosition(meta.sourceAst, { line: position.line + 1, character: position.character, }); @@ -732,19 +735,19 @@ export class Provider { const parentAst: postcss.Node | undefined = (astAtCursor as postcss.Declaration).parent ? (astAtCursor as postcss.Declaration).parent : undefined; - const parentSelector: SRule | null = + const parentSelector: postcss.Rule | null = parentAst && isSelector(parentAst) && fakeRules.findIndex((f) => { return f.selector === parentAst.selector; }) === -1 - ? (parentAst as SRule) + ? parentAst : astAtCursor && isSelector(astAtCursor) && fakeRules.findIndex((f) => { return f.selector === astAtCursor.selector; }) === -1 - ? (astAtCursor as SRule) + ? astAtCursor : null; const { lineChunkAtCursor, fixedCharIndex } = getChunkAtCursor( @@ -759,9 +762,9 @@ export class Provider { (ps.selector[0] as SelectorChunk).classes[0] || (ps.selector[0] as SelectorChunk).customSelectors[0] || chunkStrings[0]; - const expandedLine: string = expandCustomSelectors( - postcss.rule({ selector: lineChunkAtCursor }), - meta.customSelectors + const expandedLine: string = STCustomSelector.transformCustomSelectorInline( + meta, + lineChunkAtCursor ) .split(' ') .pop()!; // TODO: replace with selector parser @@ -831,7 +834,7 @@ function findRefs( const refs: Location[] = []; if (word.startsWith(':global(')) { - scannedMeta.rawAst.walkRules((rule) => { + scannedMeta.sourceAst.walkRules((rule) => { if (rule.selector.includes(word) && rule.source && rule.source.start) { refs.push({ uri: URI.file(scannedMeta.source).toString(), @@ -851,7 +854,7 @@ function findRefs( return refs; } const valueRegex = new RegExp('(\\.?' + word + ')(\\s|$|\\:|;|\\)|,)', 'g'); - scannedMeta.rawAst.walkRules((rule) => { + scannedMeta.sourceAst.walkRules((rule) => { // Usage in selector const filterRegex = new RegExp('(\\.?' + word + ')(\\s|$|\\:|;|\\))', 'g'); if (filterRegex.test(rule.selector) && !!rule.source && !!rule.source.start) { @@ -887,7 +890,7 @@ function findRefs( !!pos && resScanned[0].some((rs) => { const postcsspos = new ProviderPosition(pos.line + 1, pos.character); - const pfp = pathFromPosition(callingMeta.rawAst, postcsspos, [], true); + const pfp = pathFromPosition(callingMeta.sourceAst, postcsspos, [], true); let lastStPath = pfp[pfp.length - 1]; if (lastStPath.type === 'decl') { lastStPath = pfp[pfp.length - 2] as postcss.Rule; @@ -951,7 +954,7 @@ function findRefs( } } }); - scannedMeta.rawAst.walkDecls((decl) => { + scannedMeta.sourceAst.walkDecls((decl) => { if (!decl.source || !decl.source.start) { return; } @@ -988,13 +991,13 @@ function findRefs( } } }); - scannedMeta.rawAst.walkDecls((decl) => { + scannedMeta.sourceAst.walkDecls((decl) => { if (!decl.source || !decl.source.start || !pos) { return; } const directiveRegex = new RegExp(`-st-states`); const postcsspos = new ProviderPosition(pos.line + 1, pos.character); - const pfp = pathFromPosition(callingMeta.rawAst, postcsspos, [], true); + const pfp = pathFromPosition(callingMeta.sourceAst, postcsspos, [], true); const char = isInNode(postcsspos, pfp[pfp.length - 1]) ? 1 : pos.character; const callPs = parseSelector((pfp[pfp.length - 1] as postcss.Rule).selector, char); const callingElement = findLast( @@ -1051,7 +1054,7 @@ function findRefs( } } }); - scannedMeta.rawAst.walkDecls(`-st-mixin`, (decl) => { + scannedMeta.sourceAst.walkDecls(`-st-mixin`, (decl) => { // usage in -st-mixin if (!decl.source || !decl.source.start) { return; @@ -1092,7 +1095,7 @@ function findRefs( } }); }); - scannedMeta.rawAst.walkDecls(word, (decl) => { + scannedMeta.sourceAst.walkDecls(word, (decl) => { // Variable definition if ( decl.parent && @@ -1116,7 +1119,7 @@ function findRefs( }); } }); - scannedMeta.rawAst.walkDecls((decl) => { + scannedMeta.sourceAst.walkDecls((decl) => { // Variable usage if (decl.value.includes('value(') && !!decl.source && !!decl.source.start) { const usageRegex = new RegExp('value\\(\\s*' + word + '\\s*\\)', 'g'); @@ -1167,7 +1170,7 @@ function newFindRefs( // Global selector strings are special stylesheetsPath.forEach((stylesheetPath) => { const scannedMeta = stylable.analyze(stylesheetPath); - scannedMeta.rawAst.walkRules((rule) => { + scannedMeta.sourceAst.walkRules((rule) => { if (rule.selector.includes(word)) { refs = refs.concat(findRefs(word, defMeta, scannedMeta, callingMeta, stylable)); } @@ -1238,7 +1241,7 @@ function newFindRefs( stylesheetsPath.forEach((stylesheetPath) => { const scannedMeta = stylable.analyze(stylesheetPath); let done = false; - scannedMeta.rawAst.walkRules((r) => { + scannedMeta.sourceAst.walkRules((r) => { if (valueRegex.test(r.selector) && !done) { const resolved = stylable.transformSelector(scannedMeta, r.selector).resolved; const resolvedInner = resolved[0].find((r) => r.name === word); @@ -1259,7 +1262,7 @@ function newFindRefs( } } }); - scannedMeta.rawAst.walkDecls((d) => { + scannedMeta.sourceAst.walkDecls((d) => { if (valueRegex.test(d.value) && !done) { if ( d.prop === `-st-named` && @@ -1298,7 +1301,7 @@ function newFindRefs( Object.keys(symbolStates).some((k) => { if (k === word && !!pos) { const postcsspos = new ProviderPosition(pos.line + 1, pos.character); - const pfp = pathFromPosition(callingMeta.rawAst, postcsspos, [], true); + const pfp = pathFromPosition(callingMeta.sourceAst, postcsspos, [], true); let lastStPath = pfp[pfp.length - 1]; if (lastStPath.type === 'decl') { lastStPath = pfp[pfp.length - 2] as postcss.Rule; @@ -1353,7 +1356,7 @@ function newFindRefs( if (!pos) { return; } - scannedMeta.rawAst.walkRules((r) => { + scannedMeta.sourceAst.walkRules((r) => { if (r.selector.includes(':' + word) && !done) { // Won't work if word appears elsewhere in string const parsed = parseSelector(r.selector, r.selector.indexOf(word)); @@ -1470,7 +1473,7 @@ export function createMeta(src: string, path: string) { fakes.push(r); } - meta = stylableProcess(ast); + meta = new StylableProcessor(new Diagnostics()).process(ast); } catch (error) { return { meta: null, fakes }; } @@ -1819,9 +1822,9 @@ export function getDefSymbol( } } - const expandedLine: string = expandCustomSelectors( - postcss.rule({ selector: lineChunkAtCursor }), - meta.customSelectors + const expandedLine: string = STCustomSelector.transformCustomSelectorInline( + meta, + lineChunkAtCursor ) .split(' ') .pop()!; // TODO: replace with selector parser diff --git a/packages/language-service/test/fixtures/server-cases/states/with-param/tag/state-def-with-param-tag-start.st.css b/packages/language-service/test/fixtures/server-cases/states/with-param/tag/state-def-with-param-tag-start.st.css deleted file mode 100644 index eeebb4ec8..000000000 --- a/packages/language-service/test/fixtures/server-cases/states/with-param/tag/state-def-with-param-tag-start.st.css +++ /dev/null @@ -1,3 +0,0 @@ -.gaga{ - -st-states: hello(t|); -} diff --git a/packages/language-service/test/lib/completions/states.spec.ts b/packages/language-service/test/lib/completions/states.spec.ts index d708b6be2..04eadaa20 100644 --- a/packages/language-service/test/lib/completions/states.spec.ts +++ b/packages/language-service/test/lib/completions/states.spec.ts @@ -148,7 +148,6 @@ describe('States', () => { exp.push(createCompletion('string', rng)); exp.push(createCompletion('number', rng)); exp.push(createCompletion('enum', rng)); - exp.push(createCompletion('tag', rng)); asserter.suggested(exp); }); @@ -166,7 +165,6 @@ describe('States', () => { exp.push(createCompletion('string', rng)); unExp.push(createCompletion('number', rng)); unExp.push(createCompletion('enum', rng)); - unExp.push(createCompletion('tag', rng)); asserter.suggested(exp); asserter.notSuggested(unExp); }); @@ -297,7 +295,6 @@ describe('States', () => { exp.push(createCompletion('number', rng)); unExp.push(createCompletion('string', rng)); unExp.push(createCompletion('enum', rng)); - unExp.push(createCompletion('tag', rng)); asserter.suggested(exp); asserter.notSuggested(unExp); }); @@ -424,27 +421,6 @@ describe('States', () => { const unExp: Array> = []; exp.push(createCompletion('enum', rng)); unExp.push(createCompletion('number', rng)); - unExp.push(createCompletion('tag', rng)); - unExp.push(createCompletion('string', rng)); - asserter.suggested(exp); - asserter.notSuggested(unExp); - }); - }); - - describe('Tag', () => { - it('should complete available state with the start of a "tag" pre-written', () => { - const rng = createRange(1, 22, 1, 23); - const createCompletion = (str: string, rng: ProviderRange, path?: string) => - asserters.stateTypeDefinitionCompletion(str, rng, path); - - const asserter = asserters.getCompletions( - 'states/with-param/tag/state-def-with-param-tag-start.st.css' - ); - const exp: Array> = []; - const unExp: Array> = []; - exp.push(createCompletion('tag', rng)); - unExp.push(createCompletion('number', rng)); - unExp.push(createCompletion('enum', rng)); unExp.push(createCompletion('string', rng)); asserter.suggested(exp); asserter.notSuggested(unExp); diff --git a/packages/language-service/test/lib/diagnostics.spec.ts b/packages/language-service/test/lib/diagnostics.spec.ts index 2fd8517fa..cb87ecca2 100644 --- a/packages/language-service/test/lib/diagnostics.spec.ts +++ b/packages/language-service/test/lib/diagnostics.spec.ts @@ -23,7 +23,7 @@ describe('diagnostics', () => { end: { line: 0, character: 13 }, }, message: 'unknown pseudo-state "unknown"', - severity: 2, + severity: 1, source: 'stylable', }); }); @@ -37,7 +37,7 @@ describe('diagnostics', () => { const stylableLSP = new StylableLanguageService({ fs, - stylable: Stylable.create({ + stylable: new Stylable({ fileSystem: fs, requireModule: require, projectRoot: '/', @@ -77,7 +77,7 @@ describe('diagnostics', () => { end: { line: 3, character: 44 }, }, message: `cannot resolve imported symbol "ninja" from stylesheet ".${filePathA}"`, - severity: 2, + severity: 1, source: 'stylable', }); }); diff --git a/packages/language-service/test/lib/signatures.spec.ts b/packages/language-service/test/lib/signatures.spec.ts index 2e3cb75c7..03c759591 100644 --- a/packages/language-service/test/lib/signatures.spec.ts +++ b/packages/language-service/test/lib/signatures.spec.ts @@ -217,7 +217,7 @@ describe('Signature Help', () => { describe('State with parameters', () => { describe('definition', () => { describe('type hinting', () => { - const types = ['string', 'number', 'enum', 'tag']; + const types = ['string', 'number', 'enum']; types.forEach((str) => str.split('').forEach((_c, i) => { @@ -236,11 +236,9 @@ describe('Signature Help', () => { activeParameter: 0, signatures: [ SignatureInformation.create( - 'Supported state types:\n- "string | number | enum | tag"', + 'Supported state types:\n- "string | number | enum"', undefined, - ParameterInformation.create( - 'string | number | enum | tag' - ) + ParameterInformation.create('string | number | enum') ), ], }; @@ -262,9 +260,9 @@ describe('Signature Help', () => { activeParameter: 0, signatures: [ SignatureInformation.create( - 'Supported state types:\n- "string | number | enum | tag"', + 'Supported state types:\n- "string | number | enum"', undefined, - ParameterInformation.create('string | number | enum | tag') + ParameterInformation.create('string | number | enum') ), ], }; @@ -320,9 +318,9 @@ describe('Signature Help', () => { activeParameter: 0, signatures: [ SignatureInformation.create( - 'Supported state types:\n- "string | number | enum | tag"', + 'Supported state types:\n- "string | number | enum"', undefined, - ParameterInformation.create('string | number | enum | tag') + ParameterInformation.create('string | number | enum') ), ], }; diff --git a/packages/language-service/test/test-kit/asserters.ts b/packages/language-service/test/test-kit/asserters.ts index 41b937dc6..bd2af7a2c 100644 --- a/packages/language-service/test/test-kit/asserters.ts +++ b/packages/language-service/test/test-kit/asserters.ts @@ -45,7 +45,10 @@ export function getPath(fileName: string): postcss.Node[] { const pos = getCaretPosition(src); src = src.replace('|', ''); const proc = createMeta(src, fullPath); - return pathFromPosition(proc.meta!.rawAst, new ProviderPosition(pos.line + 1, pos.character)); + return pathFromPosition( + proc.meta!.sourceAst, + new ProviderPosition(pos.line + 1, pos.character) + ); } export function getDefinition(fileName: string): ProviderLocation[] { diff --git a/packages/language-service/test/test-kit/diagnostics-setup.ts b/packages/language-service/test/test-kit/diagnostics-setup.ts index d7bf88d4b..1cf7b7fbc 100644 --- a/packages/language-service/test/test-kit/diagnostics-setup.ts +++ b/packages/language-service/test/test-kit/diagnostics-setup.ts @@ -8,7 +8,7 @@ export function createDiagnostics(files: { [filePath: string]: string }, filePat const stylableLSP = new StylableLanguageService({ fs, - stylable: Stylable.create({ + stylable: new Stylable({ fileSystem: fs, requireModule: require, projectRoot: '/', diff --git a/packages/language-service/test/test-kit/stylable-fixtures-lsp.ts b/packages/language-service/test/test-kit/stylable-fixtures-lsp.ts index 44aa6d6e6..746d683de 100644 --- a/packages/language-service/test/test-kit/stylable-fixtures-lsp.ts +++ b/packages/language-service/test/test-kit/stylable-fixtures-lsp.ts @@ -17,7 +17,7 @@ function requireModule(request: string) { export const stylableLSP = new StylableLanguageService({ fs, - stylable: Stylable.create({ + stylable: new Stylable({ fileSystem: fs, requireModule, projectRoot: CASES_PATH, diff --git a/packages/language-service/test/test-kit/stylable-in-memory-lsp.ts b/packages/language-service/test/test-kit/stylable-in-memory-lsp.ts index fa2fbdf48..2f9337aa5 100644 --- a/packages/language-service/test/test-kit/stylable-in-memory-lsp.ts +++ b/packages/language-service/test/test-kit/stylable-in-memory-lsp.ts @@ -6,7 +6,7 @@ export function getInMemoryLSP() { const fs = createMemoryFs(); const lsp = new StylableLanguageService({ fs, - stylable: Stylable.create({ fileSystem: fs, projectRoot: '/' }), + stylable: new Stylable({ fileSystem: fs, projectRoot: '/' }), }); return { fs, lsp }; diff --git a/packages/module-utils/package.json b/packages/module-utils/package.json index 01c4c4fb1..758ddd3bc 100644 --- a/packages/module-utils/package.json +++ b/packages/module-utils/package.json @@ -1,13 +1,13 @@ { "name": "@stylable/module-utils", - "version": "4.14.0", + "version": "5.0.0", "description": "Stylable module creation utilities", "main": "dist/index.js", "scripts": { "test": "mocha \"./dist/test/**/*.spec.js\"" }, "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "@tokey/core": "^1.3.0", "vlq": "^2.0.4" }, @@ -18,7 +18,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/module-utils/src/generate-dts-sourcemaps.ts b/packages/module-utils/src/generate-dts-sourcemaps.ts index c006d4593..1f50f296f 100644 --- a/packages/module-utils/src/generate-dts-sourcemaps.ts +++ b/packages/module-utils/src/generate-dts-sourcemaps.ts @@ -18,7 +18,7 @@ function getClassSrcPosition(className: string, meta: StylableMeta): Position | let res; if (cls) { - meta.rawAst.walkRules(`.${className}`, (rule) => { + meta.sourceAst.walkRules(`.${className}`, (rule) => { if (rule.source && rule.source.start) { res = { line: rule.source.start.line - 1, column: rule.source.start.column - 1 }; return false; @@ -37,7 +37,7 @@ function getVarsSrcPosition(varName: string, meta: StylableMeta): Position | und let res; if (cssVar) { - meta.rawAst.walkDecls(cssVar.name, (decl) => { + meta.sourceAst.walkDecls(cssVar.name, (decl) => { if (decl.source && decl.source.start) { res = { line: decl.source.start.line - 1, column: decl.source.start.column - 1 }; return false; @@ -61,7 +61,7 @@ function getStVarsSrcPosition(varName: string, meta: StylableMeta): Position | u } else { // TODO: move this logic to Stylable core and enhance it. The meta should provide the API to get to the inner parts of the st-var let res: Position; - meta.rawAst.walkRules(':vars', (rule) => { + meta.sourceAst.walkRules(':vars', (rule) => { return rule.walkDecls((decl) => { if (decl.source?.start) { if (decl.prop === varName) { @@ -188,7 +188,7 @@ function createStateLineMapping( const srcClassName = findDefiningClassName(stateToken, meta.getClass(entryClassName)!); - meta.rawAst.walkRules(`.${srcClassName}`, (rule) => { + meta.sourceAst.walkRules(`.${srcClassName}`, (rule) => { return rule.walkDecls(`-st-states`, (decl) => { if (decl.source && decl.source.start) stateSourcePosition = { diff --git a/packages/module-utils/src/generate-dts.ts b/packages/module-utils/src/generate-dts.ts index 237d98d59..444fe594d 100644 --- a/packages/module-utils/src/generate-dts.ts +++ b/packages/module-utils/src/generate-dts.ts @@ -1,11 +1,9 @@ -import type { - ClassSymbol, +import type { ClassSymbol, StylableMeta, StylableResults, StylableSymbol } from '@stylable/core'; +import { + MappedStates, StateParsedValue, - StylableMeta, - StylableResults, - StylableSymbol, -} from '@stylable/core'; -import type { MappedStates } from '@stylable/core/dist/index-internal'; + namespace as scope, +} from '@stylable/core/dist/index-internal'; export const SPACING = ' '.repeat(4); const asString = (v: string) => JSON.stringify(v); @@ -130,11 +128,6 @@ function wrapNL(code: string) { return code ? `\n${code}\n` : code; } -// TODO: make available from core currently defined in transformer class -function scope(name: string, namespace: string, delimiter = '__') { - return namespace ? namespace + delimiter + name : name; -} - export function generateDTSContent({ exports, meta }: StylableResults) { const namespace = asString(meta.namespace); const classes = wrapNL(stringifyClasses(exports.classes, meta.namespace)); diff --git a/packages/module-utils/src/module-factory.ts b/packages/module-utils/src/module-factory.ts index 9a7ba675f..33940750e 100644 --- a/packages/module-utils/src/module-factory.ts +++ b/packages/module-utils/src/module-factory.ts @@ -19,9 +19,10 @@ export function stylableModuleFactory( staticImports = [], }: Partial = {} ) { - const stylable = Stylable.create(stylableOptions); + const stylable = new Stylable(stylableOptions); return function stylableToModule(source: string, path: string) { - const res = stylable.transform(source, path); + const meta = stylable.analyze(path, source); + const res = stylable.transform(meta); return generateModuleSource( res, runtimeStylesheetId === 'module' ? 'module.id' : res.meta.namespace, @@ -32,7 +33,7 @@ export function stylableModuleFactory( `runtime.$`, `runtime.create`, `runtime.createRenderable`, - injectCSS ? JSON.stringify(res.meta.outputAst!.toString()) : '""', + injectCSS ? JSON.stringify(res.meta.targetAst!.toString()) : '""', '-1', // ToDo: calc depth for node as well 'module.exports', '' /* afterModule */, diff --git a/packages/module-utils/src/module-source.ts b/packages/module-utils/src/module-source.ts index 6e349e9d3..bc0287743 100644 --- a/packages/module-utils/src/module-source.ts +++ b/packages/module-utils/src/module-source.ts @@ -51,7 +51,7 @@ export function createModuleSource( throw new Error('Configuration conflict (renderableOnly && !includeCSSInJS)'); } const cssString = includeCSSInJS - ? JSON.stringify(stylableResult.meta.outputAst!.toString()) + ? JSON.stringify(stylableResult.meta.targetAst!.toString()) : '""'; switch (moduleFormat) { diff --git a/packages/node/package.json b/packages/node/package.json index bfa288b5e..e3bf5bb52 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,14 +1,14 @@ { "name": "@stylable/node", - "version": "4.14.0", + "version": "5.0.0", "description": "Integrate Stylable into your node application", "main": "dist/index.js", "scripts": { "test": "mocha \"./dist/test/**/*.spec.js\" --timeout 10000" }, "dependencies": { - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", "find-config": "^1.0.0" }, "files": [ @@ -19,7 +19,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/node/test/require-hook.spec.ts b/packages/node/test/require-hook.spec.ts index a1bfdeef3..2ddf98578 100644 --- a/packages/node/test/require-hook.spec.ts +++ b/packages/node/test/require-hook.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { readdirSync } from 'fs'; -import { murmurhash3_32_gc } from '@stylable/core'; +import { murmurhash3_32_gc } from '@stylable/core/dist/index-internal'; import { dirname, join } from 'path'; import { attachHook } from '@stylable/node'; diff --git a/packages/optimizer/package.json b/packages/optimizer/package.json index f18e70545..d0667ecb5 100644 --- a/packages/optimizer/package.json +++ b/packages/optimizer/package.json @@ -1,6 +1,6 @@ { "name": "@stylable/optimizer", - "version": "4.14.0", + "version": "5.0.0", "description": "Stylable core optimizer", "main": "dist/index.js", "scripts": { @@ -8,7 +8,7 @@ "start": "mocha-play \"./dist/test/**/*.spec.js\" -c ./webpack.config.js -w" }, "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "@tokey/css-selector-parser": "^0.6.0", "csso": "^5.0.3", "postcss": "^8.4.14" @@ -20,7 +20,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/optimizer/src/stylable-optimizer.ts b/packages/optimizer/src/stylable-optimizer.ts index cf2179058..2035ecae4 100644 --- a/packages/optimizer/src/stylable-optimizer.ts +++ b/packages/optimizer/src/stylable-optimizer.ts @@ -1,10 +1,11 @@ +import type { StylableResults } from '@stylable/core'; import { IStylableOptimizer, OptimizeConfig, StylableExports, - StylableResults, pseudoStates, -} from '@stylable/core'; + namespaceDelimiter as delimiter, +} from '@stylable/core/dist/index-internal'; import { parseCssSelector, stringifySelectorAst, Selector, walk } from '@tokey/css-selector-parser'; import csso from 'csso'; import postcss, { Declaration, Root, Rule, Node, Comment, Container } from 'postcss'; @@ -25,16 +26,14 @@ export class StylableOptimizer implements IStylableOptimizer { public optimize( config: OptimizeConfig, stylableResults: StylableResults, - usageMapping: Record, - delimiter?: string + usageMapping: Record ) { const { - meta: { globals, outputAst: _outputAst }, + meta: { globals, targetAst }, exports: jsExports, } = stylableResults; - const outputAst = _outputAst!; - this.optimizeAst(config, outputAst, usageMapping, delimiter, jsExports, globals); + this.optimizeAst(config, targetAst as Root, usageMapping, jsExports, globals); } public getNamespace(namespace: string) { @@ -47,31 +46,29 @@ export class StylableOptimizer implements IStylableOptimizer { public optimizeAst( config: OptimizeConfig, - outputAst: Root, + targetAst: Root, usageMapping: Record, - delimiter: string | undefined, jsExports: StylableExports, globals: Record ) { if (config.removeComments) { - this.removeComments(outputAst); + this.removeComments(targetAst); } if (config.removeStylableDirectives) { - this.removeStylableDirectives(outputAst); + this.removeStylableDirectives(targetAst); } - if (config.removeUnusedComponents && usageMapping && delimiter) { - this.removeUnusedComponents(delimiter, outputAst, usageMapping); + if (config.removeUnusedComponents && usageMapping) { + this.removeUnusedComponents(targetAst, usageMapping); } if (config.removeEmptyNodes) { - this.removeEmptyNodes(outputAst); + this.removeEmptyNodes(targetAst); } this.optimizeAstAndExports( - outputAst, + targetAst, jsExports.classes, undefined, usageMapping, globals, - delimiter, config.shortNamespaces, config.classNameOptimizations ); @@ -83,18 +80,12 @@ export class StylableOptimizer implements IStylableOptimizer { classes = Object.keys(exported), usageMapping: Record, globals: Record = {}, - delimiter?: string, shortNamespaces?: boolean, classNamespaceOptimizations?: boolean ) { if (!shortNamespaces && !classNamespaceOptimizations) { return; } - if (!delimiter) { - throw new Error( - 'Missing delimiter when shortNamespaces or classNamespaceOptimizations is enabled' - ); - } ast.walkRules((rule) => { rule.selector = this.rewriteSelector( @@ -102,8 +93,7 @@ export class StylableOptimizer implements IStylableOptimizer { usageMapping, globals, shortNamespaces || false, - classNamespaceOptimizations || false, - delimiter + classNamespaceOptimizations || false ); }); const namespaceRegexp = new RegExp(`^(.*?)${delimiter}`); @@ -158,8 +148,7 @@ export class StylableOptimizer implements IStylableOptimizer { usageMapping: Record, globals: Record = {}, shortNamespaces: boolean, - classNamespaceOptimizations: boolean, - delimiter: string + classNamespaceOptimizations: boolean ) { const ast = parseCssSelector(selector); @@ -212,13 +201,12 @@ export class StylableOptimizer implements IStylableOptimizer { } private removeUnusedComponents( - delimiter: string, - outputAst: Root, + targetAst: Root, usageMapping: Record, shouldComment = false ) { const matchNamespace = new RegExp(`(.+)${delimiter}(.+)`); - outputAst.walkRules((rule) => { + targetAst.walkRules((rule) => { const outputSelectors = rule.selectors.filter((selector) => { const selectorAst = parseCssSelector(selector); return !this.isContainsUnusedParts(selectorAst[0], usageMapping, matchNamespace); diff --git a/packages/optimizer/test/ast-optimizations.spec.ts b/packages/optimizer/test/ast-optimizations.spec.ts index 41cced9a7..9a5461649 100644 --- a/packages/optimizer/test/ast-optimizations.spec.ts +++ b/packages/optimizer/test/ast-optimizations.spec.ts @@ -18,7 +18,6 @@ describe('StylableOptimizer className optimizations', () => { undefined, { namespace: true }, {}, - '__', false, true ); @@ -50,7 +49,6 @@ describe('StylableOptimizer className optimizations', () => { otherNamespace: true, }, {}, - '__', false, true ); @@ -91,7 +89,6 @@ describe('StylableOptimizer shortNamespaces', () => { undefined, { namespace: true, otherNamespace: true }, {}, - '__', true, false ); diff --git a/packages/optimizer/test/stylable-optimizer.spec.ts b/packages/optimizer/test/stylable-optimizer.spec.ts index 9283ab0ad..2dc59c9b5 100644 --- a/packages/optimizer/test/stylable-optimizer.spec.ts +++ b/packages/optimizer/test/stylable-optimizer.spec.ts @@ -69,14 +69,9 @@ describe('StylableOptimizer', () => { [result.meta.namespace]: false, }; - new StylableOptimizer().optimize( - { removeUnusedComponents: true }, - result, - usageMapping, - '__' - ); + new StylableOptimizer().optimize({ removeUnusedComponents: true }, result, usageMapping); - expect(result.meta.outputAst!.toString().trim()).to.equal(''); + expect(result.meta.targetAst!.toString().trim()).to.equal(''); }); it('minifyCSS', () => { @@ -93,7 +88,7 @@ describe('StylableOptimizer', () => { }, }; const { meta } = generateStylableResult({ entry: index, files }); - const output = new StylableOptimizer().minifyCSS(meta.outputAst!.toString()); + const output = new StylableOptimizer().minifyCSS(meta.targetAst!.toString()); expect(output).to.equal(`.${meta.namespace}__x{color:red}`); }).timeout(25000); }); diff --git a/packages/rollup-plugin/package.json b/packages/rollup-plugin/package.json index 2e12fd166..45a17e461 100644 --- a/packages/rollup-plugin/package.json +++ b/packages/rollup-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@stylable/rollup-plugin", - "version": "4.14.0", + "version": "5.0.0", "description": "Stylable plugin for Rollup", "main": "dist/index.js", "scripts": { @@ -10,12 +10,12 @@ "rollup": "^2.70.0" }, "dependencies": { - "@stylable/build-tools": "^4.14.0", - "@stylable/cli": "^4.14.0", - "@stylable/core": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/build-tools": "^5.0.0", + "@stylable/cli": "^5.0.0", + "@stylable/core": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@stylable/runtime": "^5.0.0", "decache": "^4.6.1", "mime": "^3.0.0" }, @@ -27,7 +27,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/rollup-plugin/src/index.ts b/packages/rollup-plugin/src/index.ts index 277bd31e5..d5f40cfb4 100644 --- a/packages/rollup-plugin/src/index.ts +++ b/packages/rollup-plugin/src/index.ts @@ -1,8 +1,12 @@ import type { Plugin } from 'rollup'; import fs from 'fs'; import { join, parse } from 'path'; -import { Stylable, emitDiagnostics, DiagnosticsMode } from '@stylable/core'; -import { tryCollectImportsDeep } from '@stylable/core/dist/index-internal'; +import { Stylable } from '@stylable/core'; +import { + emitDiagnostics, + DiagnosticsMode, + tryCollectImportsDeep, +} from '@stylable/core/dist/index-internal'; import { sortModulesByDepth, calcDepth, @@ -78,7 +82,7 @@ export function stylableRollupPlugin({ clearRequireCache(); stylable.initCache(); } else { - stylable = Stylable.create({ + stylable = new Stylable({ fileSystem: fs, projectRoot, mode, @@ -154,7 +158,7 @@ export function stylableRollupPlugin({ if (!id.endsWith(ST_CSS)) { return null; } - const { meta, exports } = stylable.transform(source, id); + const { meta, exports } = stylable.transform(stylable.analyze(id, source)); const assetsIds = emitAssets(this, stylable, meta, emittedAssets, inlineAssets); const css = generateCssString(meta, minify, stylable, assetsIds); const moduleImports = []; diff --git a/packages/rollup-plugin/src/plugin-utils.ts b/packages/rollup-plugin/src/plugin-utils.ts index 81f9eabc2..e115a79df 100644 --- a/packages/rollup-plugin/src/plugin-utils.ts +++ b/packages/rollup-plugin/src/plugin-utils.ts @@ -1,4 +1,5 @@ -import type { Stylable, StylableExports, StylableMeta } from '@stylable/core'; +import type { Stylable, StylableMeta } from '@stylable/core'; +import type { StylableExports } from '@stylable/core/dist/index-internal'; import type { PluginContext } from 'rollup'; import type { StylableRollupPluginOptions } from './index'; import { processUrlDependencies } from '@stylable/build-tools'; @@ -37,7 +38,7 @@ export function generateCssString( assetsIds: string[] ) { const css = meta - .outputAst!.toString() + .targetAst!.toString() .replace(/__stylable_url_asset_(.*?)__/g, (_$0, $1) => assetsIds[Number($1)]); if (minify && stylable.optimizer) { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 8384f44de..cf6526f0e 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@stylable/runtime", - "version": "4.14.0", + "version": "5.0.0", "description": "Stylable runtime DOM integration", "main": "dist/index.js", "scripts": { @@ -17,7 +17,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/schema-extract/package.json b/packages/schema-extract/package.json index 170f65763..81929b4c2 100644 --- a/packages/schema-extract/package.json +++ b/packages/schema-extract/package.json @@ -1,13 +1,13 @@ { "name": "@stylable/schema-extract", - "version": "4.14.0", + "version": "5.0.0", "description": "A utility for extracting JSON schema objects from a Stylable stylesheet", "main": "dist/index.js", "scripts": { "test": "mocha \"./dist/test/**/*.spec.js\"" }, "dependencies": { - "@stylable/core": "^4.14.0", + "@stylable/core": "^5.0.0", "jest-docblock": "^28.1.1" }, "files": [ @@ -17,7 +17,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/schema-extract/src/cssdocs.ts b/packages/schema-extract/src/cssdocs.ts index 0bbe41ccc..94cc15815 100644 --- a/packages/schema-extract/src/cssdocs.ts +++ b/packages/schema-extract/src/cssdocs.ts @@ -21,7 +21,7 @@ export function getCssDocsForSymbol(meta: StylableMeta, symbol: StylableSymbol): function extractCSSDocsToSymbolMap(meta: StylableMeta) { const docs = new Map(); - meta.rawAst.walkComments((comment) => { + meta.sourceAst.walkComments((comment) => { const node = comment.next(); if (node?.type === 'rule') { const symbol = meta.getSymbol( diff --git a/packages/schema-extract/src/main.ts b/packages/schema-extract/src/main.ts index 415d877df..177833ec9 100644 --- a/packages/schema-extract/src/main.ts +++ b/packages/schema-extract/src/main.ts @@ -1,14 +1,16 @@ -import { +import type { ClassSymbol, ElementSymbol, ImportSymbol, - cssParse, - StateParsedValue, StylableMeta, - StylableProcessor, VarSymbol, } from '@stylable/core'; -import type { MappedStates } from '@stylable/core/dist/index-internal'; +import { + MappedStates, + cssParse, + StateParsedValue, + StylableProcessor, +} from '@stylable/core/dist/index-internal'; import { getCssDocsForSymbol } from './cssdocs'; import { MinimalPath, diff --git a/packages/schema-extract/test/test.spec.ts b/packages/schema-extract/test/test.spec.ts index 754ab7ad3..0200ee19e 100644 --- a/packages/schema-extract/test/test.spec.ts +++ b/packages/schema-extract/test/test.spec.ts @@ -257,24 +257,6 @@ describe('Stylable JSON Schema Extractor', () => { }); }); - it('schema with a tags state', () => { - const css = `.root{ - -st-states: size(tag); - }`; - - const res = extractSchema(css, '/entry.st.css', '/', path); - expect(res.properties).to.eql({ - root: { - $ref: stylableClass, - states: { - size: { - type: 'tag', - }, - }, - }, - }); - }); - it('schema with mapped states', () => { const css = `.root{ -st-states: state("custom"); diff --git a/packages/uni-driver/package.json b/packages/uni-driver/package.json index 3e4fd3980..33747bbf2 100644 --- a/packages/uni-driver/package.json +++ b/packages/uni-driver/package.json @@ -1,6 +1,6 @@ { "name": "@stylable/uni-driver", - "version": "4.14.0", + "version": "5.0.0", "description": "Stylable UniDriver testing utilities", "main": "dist/index.js", "scripts": { @@ -14,7 +14,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/webpack-extensions/package.json b/packages/webpack-extensions/package.json index 22ed8b1be..07027b16f 100644 --- a/packages/webpack-extensions/package.json +++ b/packages/webpack-extensions/package.json @@ -1,6 +1,6 @@ { "name": "@stylable/webpack-extensions", - "version": "4.14.0", + "version": "5.0.0", "description": "Experimental Stylable webpack plugins", "main": "dist/index.js", "scripts": { @@ -10,9 +10,9 @@ "webpack": "^5.30.0" }, "dependencies": { - "@stylable/core": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/webpack-plugin": "^4.14.0", + "@stylable/core": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/webpack-plugin": "^5.0.0", "@tokey/css-selector-parser": "^0.6.0", "lodash.clonedeep": "^4.5.0" }, @@ -23,7 +23,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/webpack-extensions/src/create-metadata-stylesheet.ts b/packages/webpack-extensions/src/create-metadata-stylesheet.ts index a03c83552..3185b192a 100644 --- a/packages/webpack-extensions/src/create-metadata-stylesheet.ts +++ b/packages/webpack-extensions/src/create-metadata-stylesheet.ts @@ -44,7 +44,7 @@ export function rewriteImports( const sourcesByHash: Record = {}; for (const [meta, resolvedImports] of usedMeta.entries()) { const hash = ensureHash(meta, hashes); - const rawAst = meta.rawAst.clone(); + const rawAst = meta.sourceAst.clone(); for (const { resolved, stImport } of resolvedImports) { if (resolved && resolved._kind === 'css') { const rawRule = rawAst.nodes?.find(ruleByLocation(stImport.rule)); @@ -103,7 +103,7 @@ export function ensureHash(meta: StylableMeta, hashes: Map export function createContentHashPerMeta(usedMeta: Iterable) { const hashes = new Map(); for (const meta of usedMeta) { - hashes.set(meta, hashContent(meta.rawAst.toString())); + hashes.set(meta, hashContent(meta.sourceAst.toString())); } return hashes; } diff --git a/packages/webpack-extensions/src/stylable-forcestates-plugin.ts b/packages/webpack-extensions/src/stylable-forcestates-plugin.ts index 777d268b3..ba099776b 100644 --- a/packages/webpack-extensions/src/stylable-forcestates-plugin.ts +++ b/packages/webpack-extensions/src/stylable-forcestates-plugin.ts @@ -1,4 +1,4 @@ -import { nativePseudoClasses, pseudoStates } from '@stylable/core'; +import { nativePseudoClasses, pseudoStates } from '@stylable/core/dist/index-internal'; import { parseCssSelector, stringifySelectorAst, @@ -35,7 +35,7 @@ export function createDataAttr(dataAttrPrefix: string, stateName: string, param? } export function applyStylableForceStateSelectors( - outputAst: postcss.Root, + targetAst: postcss.Root, namespaceMapping: Record | ((namespace: string) => boolean) = {}, dataPrefix = OVERRIDE_STATE_PREFIX, plugin: (ctx: AddForceStateSelectorsContext) => AddForceStateSelectorsContext = (id) => id @@ -47,7 +47,7 @@ export function applyStylableForceStateSelectors( const mapping: Record = {}; addForceStateSelectors( - outputAst, + targetAst, plugin({ getForceStateAttrContentFromNative(name) { return this.getForceStateAttrContent(name); diff --git a/packages/webpack-extensions/src/stylable-manifest-plugin.ts b/packages/webpack-extensions/src/stylable-manifest-plugin.ts index 79df55c88..648351ef9 100644 --- a/packages/webpack-extensions/src/stylable-manifest-plugin.ts +++ b/packages/webpack-extensions/src/stylable-manifest-plugin.ts @@ -55,7 +55,7 @@ export class StylableManifestPlugin { this.options = Object.assign({}, defaultOptions, options); } public apply(compiler: Compiler) { - const stylable = Stylable.create({ + const stylable = new Stylable({ projectRoot: compiler.context, fileSystem: { readlinkSync: (filePath) => diff --git a/packages/webpack-extensions/src/stylable-metadata-loader.ts b/packages/webpack-extensions/src/stylable-metadata-loader.ts index e372a86d2..113bc252e 100644 --- a/packages/webpack-extensions/src/stylable-metadata-loader.ts +++ b/packages/webpack-extensions/src/stylable-metadata-loader.ts @@ -49,7 +49,7 @@ function createStylable( if (!loader._compiler) { throw new Error('Stylable metadata loader requires a compiler instance'); } - return Stylable.create({ + return new Stylable({ projectRoot: loader.rootContext, fileSystem: loader.fs as unknown as MinimalFS, mode: loader._compiler.options.mode === 'development' ? 'development' : 'production', diff --git a/packages/webpack-extensions/test/unit/forcestates-plugin-plugins.spec.ts b/packages/webpack-extensions/test/unit/forcestates-plugin-plugins.spec.ts index 6fe2f219a..e46a7114a 100644 --- a/packages/webpack-extensions/test/unit/forcestates-plugin-plugins.spec.ts +++ b/packages/webpack-extensions/test/unit/forcestates-plugin-plugins.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import type * as postcss from 'postcss'; import { applyStylableForceStateSelectors } from '@stylable/webpack-extensions'; -import { cssParse } from '@stylable/core'; +import { cssParse } from '@stylable/core/dist/index-internal'; describe('stylable-forcestates plugins', () => { it('basic native plugin support', () => { diff --git a/packages/webpack-extensions/test/unit/forcestates-plugin.spec.ts b/packages/webpack-extensions/test/unit/forcestates-plugin.spec.ts index f8d34df54..0e600bcea 100644 --- a/packages/webpack-extensions/test/unit/forcestates-plugin.spec.ts +++ b/packages/webpack-extensions/test/unit/forcestates-plugin.spec.ts @@ -96,11 +96,11 @@ describe('stylable-forcestates-plugin', () => { }, }); - applyStylableForceStateSelectors(res.meta.outputAst!, { + applyStylableForceStateSelectors(res.meta.targetAst!, { entry: true, }); - expect((res.meta.outputAst!.nodes[1] as postcss.Rule).selector).to.equal( + expect((res.meta.targetAst!.nodes[1] as postcss.Rule).selector).to.equal( '.entry__root.entry--myState,.entry__root[stylable-force-state-myState]' ); }); @@ -123,11 +123,11 @@ describe('stylable-forcestates-plugin', () => { }, }); - applyStylableForceStateSelectors(res.meta.outputAst!, (name) => { + applyStylableForceStateSelectors(res.meta.targetAst!, (name) => { return name === 'entry'; }); - expect((res.meta.outputAst!.nodes[1] as postcss.Rule).selector).to.equal( + expect((res.meta.targetAst!.nodes[1] as postcss.Rule).selector).to.equal( '.entry__root.entry--myState,.entry__root[stylable-force-state-myState]' ); }); @@ -149,11 +149,11 @@ describe('stylable-forcestates-plugin', () => { }, }); - applyStylableForceStateSelectors(res.meta.outputAst!, { + applyStylableForceStateSelectors(res.meta.targetAst!, { entry: true, }); - expect((res.meta.outputAst!.nodes[1] as postcss.Rule).selector).to.equal( + expect((res.meta.targetAst!.nodes[1] as postcss.Rule).selector).to.equal( '.entry__root:hover,.entry__root[stylable-force-state-hover]' ); }); @@ -177,11 +177,11 @@ describe('stylable-forcestates-plugin', () => { }, }); - applyStylableForceStateSelectors(res.meta.outputAst!, { + applyStylableForceStateSelectors(res.meta.targetAst!, { entry: true, }); - expect((res.meta.outputAst!.nodes[1] as postcss.Rule).selector).to.equal( + expect((res.meta.targetAst!.nodes[1] as postcss.Rule).selector).to.equal( `.entry__root.entry---myState-5-value,.entry__root[${createDataAttr( OVERRIDE_STATE_PREFIX, 'myState', @@ -209,11 +209,11 @@ describe('stylable-forcestates-plugin', () => { }, }); - applyStylableForceStateSelectors(res.meta.outputAst!, { + applyStylableForceStateSelectors(res.meta.targetAst!, { entry: true, }); - expect((res.meta.outputAst!.nodes[1] as postcss.Rule).selector).to.equal( + expect((res.meta.targetAst!.nodes[1] as postcss.Rule).selector).to.equal( `.entry__root.entry---myState-10-some_value,.entry__root[${createDataAttr( OVERRIDE_STATE_PREFIX, 'myState', diff --git a/packages/webpack-plugin/package.json b/packages/webpack-plugin/package.json index 05e3dbfac..cf73f9d61 100644 --- a/packages/webpack-plugin/package.json +++ b/packages/webpack-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@stylable/webpack-plugin", "description": "Webpack (5.x) plugin for Stylable modules", - "version": "4.14.0", + "version": "5.0.0", "main": "dist/index.js", "scripts": { "test": "mocha \"./dist/test/**/*.spec.js\" --timeout 20000" @@ -10,13 +10,13 @@ "webpack": "^5.30.0" }, "dependencies": { - "@stylable/build-tools": "^4.14.0", - "@stylable/cli": "^4.14.0", - "@stylable/core": "^4.14.0", - "@stylable/module-utils": "^4.14.0", - "@stylable/node": "^4.14.0", - "@stylable/optimizer": "^4.14.0", - "@stylable/runtime": "^4.14.0", + "@stylable/build-tools": "^5.0.0", + "@stylable/cli": "^5.0.0", + "@stylable/core": "^5.0.0", + "@stylable/module-utils": "^5.0.0", + "@stylable/node": "^5.0.0", + "@stylable/optimizer": "^5.0.0", + "@stylable/runtime": "^5.0.0", "decache": "^4.6.1", "find-config": "^1.0.0", "lodash.clonedeep": "^4.5.0", @@ -29,7 +29,7 @@ "!*/tsconfig.{json,tsbuildinfo}" ], "engines": { - "node": ">=12" + "node": ">=14.14.0" }, "publishConfig": { "access": "public" diff --git a/packages/webpack-plugin/src/loader.ts b/packages/webpack-plugin/src/loader.ts index 1f1cba9bb..187ef4488 100644 --- a/packages/webpack-plugin/src/loader.ts +++ b/packages/webpack-plugin/src/loader.ts @@ -1,9 +1,11 @@ import { getImports, getReplacementToken } from './loader-utils'; import type { StylableLoaderContext } from './types'; -import { emitDiagnostics } from '@stylable/core'; +import { emitDiagnostics } from '@stylable/core/dist/index-internal'; export default function StylableWebpackLoader(this: StylableLoaderContext, source: string) { - const { meta, exports } = this.stylable.transform(source, this.resourcePath); + const { meta, exports } = this.stylable.transform( + this.stylable.analyze(this.resourcePath, source) + ); const { urls, imports, buildDependencies, unusedImports } = getImports( this.stylable, @@ -21,7 +23,7 @@ export default function StylableWebpackLoader(this: StylableLoaderContext, sourc const varType = this.target === 'oldie' ? 'var' : 'const'; this.flagStylableModule({ - css: meta.outputAst!.toString(), + css: meta.targetAst!.toString(), globals: meta.globals, exports, namespace: meta.namespace, diff --git a/packages/webpack-plugin/src/plugin-utils.ts b/packages/webpack-plugin/src/plugin-utils.ts index 65d5cc78b..41bca7bab 100644 --- a/packages/webpack-plugin/src/plugin-utils.ts +++ b/packages/webpack-plugin/src/plugin-utils.ts @@ -18,7 +18,7 @@ import type { WebpackCreateHash, WebpackOutputOptions, } from './types'; -import type { IStylableOptimizer, StylableResolverCache } from '@stylable/core'; +import type { IStylableOptimizer, StylableResolverCache } from '@stylable/core/dist/index-internal'; import decache from 'decache'; import { CalcDepthContext, getCSSViewModule } from '@stylable/build-tools'; import { join, parse } from 'path'; diff --git a/packages/webpack-plugin/src/plugin.ts b/packages/webpack-plugin/src/plugin.ts index 45970d018..cbd6cb205 100644 --- a/packages/webpack-plugin/src/plugin.ts +++ b/packages/webpack-plugin/src/plugin.ts @@ -1,10 +1,9 @@ -import { - Stylable, - StylableConfig, +import { Stylable, StylableConfig } from '@stylable/core'; +import type { OptimizeConfig, DiagnosticsMode, IStylableOptimizer, -} from '@stylable/core'; +} from '@stylable/core/dist/index-internal'; import { createNamespaceStrategyNode } from '@stylable/node'; import { sortModulesByDepth, loadStylableConfig, calcDepth } from '@stylable/build-tools'; import { StylableOptimizer } from '@stylable/optimizer'; @@ -342,7 +341,7 @@ export class StylableWebpackPlugin { compiler.options.resolve.aliasFields, }; - this.stylable = Stylable.create( + this.stylable = new Stylable( this.options.stylableConfig( { projectRoot: compiler.context, @@ -428,7 +427,10 @@ export class StylableWebpackPlugin { * If STC Builder is running in background we need to add the relevant files to webpack file dependencies watcher, * and emit diagnostics from the sources and not from the output. */ - const sources = this.stcBuilder?.getSourcesFiles(module.resource); + if (!this.stcBuilder) { + return; + } + const sources = this.stcBuilder.getSourcesFiles(module.resource); if (sources) { /** @@ -445,7 +447,7 @@ export class StylableWebpackPlugin { /** * Add source file diagnostics to the output file module (more accurate diagnostic) */ - this.stcBuilder!.reportDiagnostic( + this.stcBuilder.reportDiagnostic( sourceFilePath, loaderContext, this.options.diagnosticsMode, @@ -557,7 +559,6 @@ export class StylableWebpackPlugin { optimizeOptions, ast, usageMapping, - this.stylable.delimiter, buildData.exports, globals ); diff --git a/packages/webpack-plugin/src/types.ts b/packages/webpack-plugin/src/types.ts index ec24a4075..c151574a5 100644 --- a/packages/webpack-plugin/src/types.ts +++ b/packages/webpack-plugin/src/types.ts @@ -1,4 +1,5 @@ -import type { DiagnosticsMode, Stylable, StylableExports } from '@stylable/core'; +import type { Stylable } from '@stylable/core'; +import type { DiagnosticsMode, StylableExports } from '@stylable/core/dist/index-internal'; import type { Chunk, Compilation, Compiler, LoaderContext } from 'webpack'; export interface StylableBuildMeta { diff --git a/packages/webpack-plugin/test/e2e/errors-project.spec.ts b/packages/webpack-plugin/test/e2e/errors-project.spec.ts index 31d23ea55..5b03e4c50 100644 --- a/packages/webpack-plugin/test/e2e/errors-project.spec.ts +++ b/packages/webpack-plugin/test/e2e/errors-project.spec.ts @@ -24,9 +24,11 @@ describe(`(${project})`, () => { it('emit stylable errors/warnings as webpack errors/warnings', () => { const errors = projectRunner.getBuildErrorMessages(); const warnings = projectRunner.getBuildWarningMessages(); - expect(errors, 'should only have one error').to.have.lengthOf(1); - expect(errors[0]).to.match(/cannot extend unknown symbol "NotFound"/); - expect(warnings, 'should only have two warnings').to.have.lengthOf(2); - expect(warnings[1]).to.match(/unknown pseudo-state "unknown-state"/); + expect(errors, 'should only have two error').to.have.lengthOf(2); + expect(errors[0]).to.match(/\[error: \d+\]: cannot extend unknown symbol "NotFound"/); + expect(warnings, 'should only have one warnings').to.have.lengthOf(1); + expect(warnings[0]).to.match( + /\[warning: \d+\]: unscoped type selector "NotFound" will affect all elements of the same type in the document/ + ); }); }); diff --git a/packages/webpack-plugin/test/e2e/namespace-generation-project.ts b/packages/webpack-plugin/test/e2e/namespace-generation-project.ts index 550361d7e..a6d4d1716 100644 --- a/packages/webpack-plugin/test/e2e/namespace-generation-project.ts +++ b/packages/webpack-plugin/test/e2e/namespace-generation-project.ts @@ -1,6 +1,6 @@ import { StylableProjectRunner } from '@stylable/e2e-test-kit'; import { expect } from 'chai'; -import { murmurhash3_32_gc } from '@stylable/core'; +import { murmurhash3_32_gc } from '@stylable/core/dist/index-internal'; import { dirname } from 'path'; const project = 'namespace-generation-project'; diff --git a/packages/webpack-plugin/test/e2e/projects/autoprefixer-project/webpack.config.js b/packages/webpack-plugin/test/e2e/projects/autoprefixer-project/webpack.config.js index 5a7bf70f9..e8d44af1b 100644 --- a/packages/webpack-plugin/test/e2e/projects/autoprefixer-project/webpack.config.js +++ b/packages/webpack-plugin/test/e2e/projects/autoprefixer-project/webpack.config.js @@ -17,7 +17,7 @@ module.exports = { ...config, hooks: { postProcessor: (stylableResult) => { - autoprefixProcessor.process(stylableResult.meta.outputAst).sync(); + autoprefixProcessor.process(stylableResult.meta.targetAst).sync(); return stylableResult; }, }, diff --git a/packages/webpack-plugin/test/e2e/projects/emit-info-diagnostic/webpack.config.js b/packages/webpack-plugin/test/e2e/projects/emit-info-diagnostic/webpack.config.js index 33be2743f..931c5b5c2 100644 --- a/packages/webpack-plugin/test/e2e/projects/emit-info-diagnostic/webpack.config.js +++ b/packages/webpack-plugin/test/e2e/projects/emit-info-diagnostic/webpack.config.js @@ -14,9 +14,13 @@ module.exports = { hooks: { postProcessor: (result) => { // Todo: replace implementation with permanent info diagnostic - result.meta.diagnostics.info( - result.meta.ast.root(), - 'test info diagnostic!' + result.meta.diagnostics.report( + { + code: '99999', + message: 'test info diagnostic!', + severity: 'info', + }, + { node: result.meta.sourceAst.root() } ); return result; }, diff --git a/packages/webpack-plugin/test/e2e/projects/errors-project/src/index.st.css b/packages/webpack-plugin/test/e2e/projects/errors-project/src/index.st.css index 5996edbe0..38a32132d 100644 --- a/packages/webpack-plugin/test/e2e/projects/errors-project/src/index.st.css +++ b/packages/webpack-plugin/test/e2e/projects/errors-project/src/index.st.css @@ -5,7 +5,7 @@ .root { -st-extends: NotFound; - background-color: green; + background-color: green; } -.root:unknown-state {} +NotFound {} diff --git a/packages/webpack-plugin/test/e2e/projects/hooked-project/stylable.config.js b/packages/webpack-plugin/test/e2e/projects/hooked-project/stylable.config.js index 1e40be8b4..f1e785c83 100644 --- a/packages/webpack-plugin/test/e2e/projects/hooked-project/stylable.config.js +++ b/packages/webpack-plugin/test/e2e/projects/hooked-project/stylable.config.js @@ -8,7 +8,7 @@ module.exports.webpackPlugin = function (options) { hooks: { postProcessor(result) { const actions = []; - result.meta.outputAst.walkDecls((decl) => { + result.meta.targetAst.walkDecls((decl) => { actions.push(() => decl.after( decl.clone({ diff --git a/packages/webpack-plugin/test/e2e/projects/optimizations/src/index.st.css b/packages/webpack-plugin/test/e2e/projects/optimizations/src/index.st.css index 63fa1eeda..5740ca256 100644 --- a/packages/webpack-plugin/test/e2e/projects/optimizations/src/index.st.css +++ b/packages/webpack-plugin/test/e2e/projects/optimizations/src/index.st.css @@ -15,10 +15,15 @@ background-color: rgb(228, 228, 228); } -.root ::part(name) Button {color: gold;} +/* unused component */ +Button { + color: gold; +} .root { - -st-states: /*x state*/ x; + -st-states: + /*x state*/ + x; } .root:x { @@ -42,8 +47,9 @@ Button { .empty-in-media { /* empty rule */ } + Button { /* unused component */ font-size: 2em; } -} \ No newline at end of file +} diff --git a/pleb.config.mjs b/pleb.config.mjs index 7c62283c8..ce3ba6f94 100644 --- a/pleb.config.mjs +++ b/pleb.config.mjs @@ -1,9 +1,3 @@ export default { - pinnedPackages: [ - { name: 'jsdom', reason: 'drops support for node v12' }, - { name: 'source-map-loader', reason: 'drops support for node v12' }, - { name: '@wixc3/resolve-directory-context', reason: 'drops support for node v12' }, - { name: 'vscode-languageserver', reason: 'drops support for node v12' }, - { name: 'vscode-css-languageservice', reason: 'drops support for node v12' }, - ], + pinnedPackages: [{ name: 'jsdom', reason: 'definitely typed package has not been updated yet' }], };