diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index f7bb9b66d2..8a0d303472 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -213,15 +213,15 @@ jobs: matrix: unit-tests: - name: content-services - exclude: "insights,core,extensions,process-services,process-services-cloud" + exclude: "insights,core,extensions,process-services,process-services-cloud,eslint-plugin-eslint-angular" - name: core - exclude: "insights,content-services,process-services,process-services-cloud" + exclude: "insights,content-services,process-services,process-services-cloud,eslint-plugin-eslint-angular" - name: insights - exclude: "core,extensions,content-services,process-services-cloud,process-services" + exclude: "core,extensions,content-services,process-services-cloud,process-services,eslint-plugin-eslint-angular" - name: process-services - exclude: "core,extensions,content-services,process-services-cloud,insights" + exclude: "core,extensions,content-services,process-services-cloud,insights,eslint-plugin-eslint-angular" - name: process-cloud - exclude: "insights,core,extensions,content-services,process-services" + exclude: "insights,core,extensions,content-services,process-services,eslint-plugin-eslint-angular" steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/angular.json b/angular.json index 1a9862e5c0..235a3fda3d 100644 --- a/angular.json +++ b/angular.json @@ -1020,6 +1020,67 @@ } } }, + "eslint-angular": { + "root": "lib/eslint-angular", + "sourceRoot": "lib/eslint-angular/src", + "projectType": "library", + "prefix": "adf", + "architect": { + "build": { + "builder": "@nrwl/node:webpack", + "options": { + "projectRoot": "lib/eslint-angular", + "outputPath": "dist/libs/eslint-plugin-eslint-angular", + "main": "lib/eslint-angular/index.ts", + "generatePackageJson" : true, + "tsConfig": "lib/eslint-angular/tsconfig.lib.prod.json" + }, + "configurations": { + "production": { + "projectRoot": "lib/eslint-angular", + "outputPath": "dist/libs/eslint-plugin-eslint-angular", + "main": "lib/eslint-angular/index.ts", + "generatePackageJson" : true, + "tsConfig": "lib/eslint-angular/tsconfig.lib.prod.json" + } + }, + "defaultConfiguration": "production" + }, + "lint": { + "builder": "@nrwl/linter:eslint", + "options": { + "lintFilePatterns": [ + "lib/eslint-angular/**/*.ts" + ] + } + }, + "bundle": { + "executor": "nx:run-commands", + "options": { + "commands": [ + { + "command": "echo testing bundle created" + } + ] + } + }, + "npm-publish": { + "executor": "nx:run-commands", + "dependsOn": [ + "build" + ], + "options": { + "cwd": "dist/libs/eslint-angular", + "commands": [ + { + "command": "npm publish --tag {args.tag}", + "forwardAllArgs": true + } + ] + } + } + } + }, "cli": { "root": "lib/cli", "sourceRoot": "lib/cli", diff --git a/docs/README.md b/docs/README.md index 83d8e4ca07..c45943bb49 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,6 +51,7 @@ A few other pages of information are also available: - [Process Services Cloud API](#process-services-cloud-api) - [Extensions API](#extensions-api) - [Insights API](#insights-api) +- [ESLint Angular API](#eslint-angular-api) ## User guide @@ -596,3 +597,19 @@ for more information about installing and using the source code. [(Back to Contents)](#contents) + +## ESLint Angular API + +Contains all custom rules used by ESLint. + + + +### Rules + +| Name | Description | Source link | +|--------------------------------------------------------------------------------------------------------|-------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------| +| [Use none component view encapsulation](eslint-angular/rules/use-none-component-view-encapsulation.md) | Enforces using ViewEncapsulation.None for components. | [Source](../lib/eslint-angular/src/rules/use-none-component-view-encapsulation/use-none-component-view-encapsulation.ts) | + + + +[(Back to Contents)](#contents) diff --git a/docs/eslint-angular/rules/use-none-component-view-encapsulation.md b/docs/eslint-angular/rules/use-none-component-view-encapsulation.md new file mode 100644 index 0000000000..72e30dd491 --- /dev/null +++ b/docs/eslint-angular/rules/use-none-component-view-encapsulation.md @@ -0,0 +1,22 @@ +--- +Title: Use none component view encapsulation +Added: v6.0.0 +Status: Active +Last reviewed: 2023-05-23 +--- + +# [Use none component view encapsulation](../../../lib/eslint-angular/src/rules/use-none-component-view-encapsulation/use-none-component-view-encapsulation.ts "Defined in use-none-component-view-encapsulation.ts") + +Custom ESLint rule which check if component uses ViewEncapsulation.None. It has been implemented because None encapsulation makes themes styling easier. +It also allows to autofix. + +## Basic Usage +Put this rule in eslintrc.json in rules. + +```json +{ + "rules": { + "@alfresco/eslint-angular/use-none-component-view-encapsulation": "error" + } +} +``` diff --git a/docs/versionIndex.md b/docs/versionIndex.md index 06eb28c429..3b1f9011db 100644 --- a/docs/versionIndex.md +++ b/docs/versionIndex.md @@ -41,7 +41,7 @@ backend services have been tested with each released version of ADF. - [v2.1.0](#v210) - [v2.0.0](#v200) -## v5.1.0 +## v6.0.0 @@ -54,6 +54,7 @@ backend services have been tested with each released version of ADF. - [Process user info component](process-services/components/process-user-info.component.md) - [Task comments service](content-services/services/task-comments.service.md) - [Viewer render component](core/components/viewer-render.component.md) +- [Use none component view encapsulation](eslint-angular/rules/use-none-component-view-encapsulation.md) diff --git a/lib/eslint-angular/index.ts b/lib/eslint-angular/index.ts new file mode 100644 index 0000000000..d5f855bf1a --- /dev/null +++ b/lib/eslint-angular/index.ts @@ -0,0 +1,26 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import useNoneEncapsulationRule, { + RULE_NAME as useNoneEncapsulationRuleName +} from './src/rules/use-none-component-view-encapsulation/use-none-component-view-encapsulation'; + +export = { + rules: { + [useNoneEncapsulationRuleName]: useNoneEncapsulationRule + } +}; diff --git a/lib/eslint-angular/ng-package.json b/lib/eslint-angular/ng-package.json new file mode 100644 index 0000000000..07d539d12a --- /dev/null +++ b/lib/eslint-angular/ng-package.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist/libs/eslint-angular", + "lib": { + "entryFile": "src/public-api.ts", + "flatModuleFile": "eslint-plugin-eslint-angular" + } +} diff --git a/lib/eslint-angular/package.json b/lib/eslint-angular/package.json new file mode 100644 index 0000000000..60f5da2a00 --- /dev/null +++ b/lib/eslint-angular/package.json @@ -0,0 +1,19 @@ +{ + "name": "@alfresco/eslint-plugin-eslint-angular", + "version": "6.0.0", + "description": "Alfresco ADF eslint angular custom rules", + "main": "main.js", + "author": "Hyland Software, Inc. and its affiliates", + "repository": { + "type": "git", + "url": "https://github.com/Alfresco/alfresco-ng2-components.git" + }, + "bugs": { + "url": "https://github.com/Alfresco/alfresco-ng2-components/issues" + }, + "keywords": [ + "eslint", + "eslint-custom-rules" + ], + "license": "Apache-2.0" +} diff --git a/lib/eslint-angular/src/rules/use-none-component-view-encapsulation/use-none-component-view-encapsulation.ts b/lib/eslint-angular/src/rules/use-none-component-view-encapsulation/use-none-component-view-encapsulation.ts new file mode 100644 index 0000000000..c6fccc041f --- /dev/null +++ b/lib/eslint-angular/src/rules/use-none-component-view-encapsulation/use-none-component-view-encapsulation.ts @@ -0,0 +1,123 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ASTUtils, isNotNullOrUndefined, RuleFixes, Selectors } from '@angular-eslint/utils'; +import type { TSESTree } from '@typescript-eslint/utils'; +import { createESLintRule } from '../../utils/create-eslint-rule/create-eslint-rule'; + +export const RULE_NAME = 'use-none-component-view-encapsulation'; + +type MessageIds = 'useNoneComponentViewEncapsulation'| 'suggestAddViewEncapsulationNone'; +type DecoratorForClass = TSESTree.Decorator & { + parent: TSESTree.ClassDeclaration; +}; +type PropertyInClassDecorator = TSESTree.Property & { + parent: TSESTree.CallExpression & { + parent: TSESTree.ObjectExpression & { + parent: TSESTree.Decorator & { + parent: TSESTree.ClassDeclaration; + }; + }; + }; +}; + +const metadataPropertyName = 'encapsulation'; +const viewEncapsulationNone = 'ViewEncapsulation.None'; +const nodeToReport = (node: TSESTree.Node) => { + if (!ASTUtils.isProperty(node)) { + return node; + } + return ASTUtils.isMemberExpression(node.value) ? node.value.property : node.value; +}; + +/** + * Custom ESLint rule which check if component uses ViewEncapsulation.None. It has been implemented because None encapsulation makes themes styling easier. + * It also allows to autofix. + */ +export default createESLintRule({ + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: `Disallows using other encapsulation than \`${viewEncapsulationNone}\``, + recommended: false + }, + hasSuggestions: true, + schema: [], + messages: { + useNoneComponentViewEncapsulation: `Using encapsulation other than '${viewEncapsulationNone}' makes themes styling harder.`, + suggestAddViewEncapsulationNone: `Add '${viewEncapsulationNone}'` + } + }, + defaultOptions: [], + create(context) { + const encapsulationProperty = Selectors.metadataProperty( + metadataPropertyName + ); + const withoutEncapsulationProperty = + `${Selectors.COMPONENT_CLASS_DECORATOR}:matches([expression.arguments.length=0], [expression.arguments.0.type='ObjectExpression']:not(:has(${encapsulationProperty})))` as const; + const nonNoneViewEncapsulationNoneProperty = + `${Selectors.COMPONENT_CLASS_DECORATOR} > CallExpression > ObjectExpression > ` + + `${encapsulationProperty}:matches([value.type='Identifier'][value.name='undefined'], [value.object.name='ViewEncapsulation'][value.property.name!='None'])`; + const selectors = [ + withoutEncapsulationProperty, + nonNoneViewEncapsulationNoneProperty + ].join(','); + return { + [selectors](node: DecoratorForClass | PropertyInClassDecorator) { + context.report({ + node: nodeToReport(node), + messageId: 'useNoneComponentViewEncapsulation', + suggest: [ + { + messageId: 'suggestAddViewEncapsulationNone', + fix: (fixer) => { + if (ASTUtils.isProperty(node)) { + return [ + RuleFixes.getImportAddFix({ + fixer, + importName: 'ViewEncapsulation', + moduleName: '@angular/core', + node: node.parent.parent.parent.parent + }), + ASTUtils.isMemberExpression(node.value) + ? fixer.replaceText(node.value.property, 'None') + : fixer.replaceText(node.value, viewEncapsulationNone) + ].filter(isNotNullOrUndefined); + } + + return [ + RuleFixes.getImportAddFix({ + fixer, + importName: 'ViewEncapsulation', + moduleName: '@angular/core', + node: node.parent + }), + RuleFixes.getDecoratorPropertyAddFix( + node, + fixer, + `${metadataPropertyName}: ${viewEncapsulationNone}` + ) + ].filter(isNotNullOrUndefined); + } + } + ] + }); + } + }; + } +}); diff --git a/lib/eslint-angular/src/utils/create-eslint-rule/create-eslint-rule.ts b/lib/eslint-angular/src/utils/create-eslint-rule/create-eslint-rule.ts new file mode 100644 index 0000000000..dcca68dcde --- /dev/null +++ b/lib/eslint-angular/src/utils/create-eslint-rule/create-eslint-rule.ts @@ -0,0 +1,20 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ESLintUtils } from '@typescript-eslint/utils'; + +export const createESLintRule = ESLintUtils.RuleCreator((ruleName) => ruleName); diff --git a/lib/eslint-angular/tsconfig.json b/lib/eslint-angular/tsconfig.json new file mode 100644 index 0000000000..e92b7325f4 --- /dev/null +++ b/lib/eslint-angular/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2019", + "module": "commonjs", + "sourceMap": false, + "outDir": "dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noUnusedLocals": true, + "skipLibCheck": true, + "baseUrl": "src", + "types": [ + "node" + ] + }, + "include": [ + "**/*" + ], + "exclude": [ + "node_modules", + "tests" + ] +} diff --git a/lib/eslint-angular/tsconfig.lib.json b/lib/eslint-angular/tsconfig.lib.json new file mode 100644 index 0000000000..48bbd83c1e --- /dev/null +++ b/lib/eslint-angular/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declarationMap": true + }, + "exclude": ["src/test.ts", "**/*.spec.ts", "**/*.test.ts"], + "include": ["**/*.ts"] +} diff --git a/lib/eslint-angular/tsconfig.lib.prod.json b/lib/eslint-angular/tsconfig.lib.prod.json new file mode 100644 index 0000000000..2a2faa884c --- /dev/null +++ b/lib/eslint-angular/tsconfig.lib.prod.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": { + "compilationMode": "partial" + } +} diff --git a/lib/eslint-angular/tsconfig.spec.json b/lib/eslint-angular/tsconfig.spec.json new file mode 100644 index 0000000000..8e8cc9494b --- /dev/null +++ b/lib/eslint-angular/tsconfig.spec.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc" + }, + "files": ["src/test.ts"], + "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts"] +} diff --git a/scripts/github/build/npm-check-bundles.sh b/scripts/github/build/npm-check-bundles.sh index b1fc85af46..496de59bfe 100755 --- a/scripts/github/build/npm-check-bundles.sh +++ b/scripts/github/build/npm-check-bundles.sh @@ -10,7 +10,8 @@ eval projects=( "adf-core" "adf-extensions" "adf-testing" "adf-process-services" - "adf-process-services-cloud" ) + "adf-process-services-cloud", + "eslint-plugin-eslint-angular" ) show_help() { echo "Usage: npm-check-bundles.sh" diff --git a/scripts/update-version.sh b/scripts/update-version.sh index 997b252cbc..953a505fb0 100755 --- a/scripts/update-version.sh +++ b/scripts/update-version.sh @@ -17,7 +17,8 @@ eval projects=( "cli" "process-services-cloud" "insights" "testing" - "extensions" ) + "extensions", + "eslint-plugin-eslint-angular" ) cd `dirname $0`