Optimization of code quality through the use of linting
In this blog article we will deal with linting explain how it works in Frontend and PHP development and how it can be integrated into Shopware 6 projects.

Linting is an important aspect of modern programming and is being used more and more frequently in web development. It is a type of static code analysis that checks the code for errors before the program is executed. The use of linting improves the quality of the code, speeds up development and avoids errors. In Shopware 6 development in particular, linting is essential for writing clean and error-free code.
But what exactly is linting?
This is a process of static code analysis in which special tools are used to check the code for syntax errors, stylistic errors and potential sources of error. The tool used for linting is called a linter. A linter scans the code and displays errors and potential problems. Some of the most common errors that can be found by linting are undeclared variables, unused variables, declared but never called functions, loops without exit clauses and invalid syntax.
Frontend-Linting
In particular, Frontend is about checking HTML, CSS and JavaScript code.
In our Shopware projects, we use Stylelint, Eslint and Prettier for the Frontend-linting to ensure that our code language remains uniform, consistent standards are maintained and potential problems can be identified. Adding linting tools to our Frontend workflow has many benefits. It increases efficiency by reducing time-consuming manual code reviews and detecting errors early on. It also helps us to utilize code style tricks, such as CSS and JavaScript best practices, which improve the quality of our code and eliminate errors in the later stages. Our Frontend team can focus on building features instead of worrying about avoidable errors, which ultimately leads to higher productivity and quality of our Frontend.
Stylelint
Stylelint is a tool for linking CSS/SCSS/LESS code that was specially developed for this purpose. In the Shopware team, we decided to use the stylelint configuration of Shopware 6 and adapted it to our needs. Although Shopware 6 already uses an optimized stylelint configuration, we have further adapted it to ensure that it is specifically optimized for our workflows and stylesheets. This allows us to ensure that our code is clean, consistent and compliant with the latest best practices.
const config =
require('../../vendor/shopware/storefront/Resources/app/storefront/stylelint.config');
module.exports = Object.assign(config, {
rules: Object.assign(config.rules, {
'max-nesting-depth': 8,
'selector-max-compound-selectors': 8,
}),
});
vendor/shopware/storefront/Resources/app/storefront/node_modules/stylelint/bin/stylelint.js \
--config stylelint.config.js \
--ignore-path .stylelintignore \
--config-basedir vendor/shopware/storefront/Resources/app/storefront \
--allow-empty-input ./custom/**/Sitegeist*/src/**/storefront/src/**/*.scss
ESLint
ESLint is a flexible and customizable linter tool for JavaScript code. With the help of rules, ESLint can check the code in many ways, e.g. for misspelled variable names or the use of undefined variables. It is an excellent tool for Frontend development and can be easily integrated into workflows. In the Shopware team, we have adapted the ESLint configuration provided by Shopware for Shopware 6 to our own needs. We have added or removed specific rules to ensure that our code follows our own internal best practices and conforms to the standards we have set for our projects. We have also integrated additional plugins to ensure that certain types of errors or issues are detected that are relevant to our projects.
const config =
require('../../vendor/shopware/storefront/Resources/app/storefront/.eslintrc');
delete config.env['jest/globals'];
Object.keys(config.rules).forEach(key => {
if (key.includes('jest/')) {
delete config.rules[key];
}
});
module.exports = Object.assign(config, {
parser: '../../vendor/shopware/storefront/Resources/app/storefront/node_modules/babel-eslint',
plugins: [],
rules: Object.assign(config.rules, {
'array-callback-return': 'error',
'no-duplicate-imports': 'error',
'no-use-before-define': 'error',
'camelcase': 'error',
'consistent-this': ['error', 'self'],
'curly': 'error',
'default-param-last': 'error',
'no-else-return': 'error',
'no-eval': 'error',
'no-implicit-coercion': 'error',
'no-lone-blocks': 'error',
'no-lonely-if': 'error',
'no-multi-assign': 'error',
'no-nested-ternary': 'error',
'no-new-wrappers': 'error',
'no-param-reassign': 'error',
'no-return-assign': 'error',
'no-script-url': 'error',
'no-sequences': 'error',
'no-shadow': 'error',
'no-undef-init': 'error',
'no-unneeded-ternary': 'error',
'no-unused-expressions': 'error',
'no-useless-call': 'error',
'no-useless-computed-key': 'error',
'no-useless-concat': 'error',
'no-useless-rename': 'error',
'no-useless-return': 'error',
'no-var': 'error',
'prefer-const': 'error',
'prefer-template': 'error',
'quote-props': ['warn', 'consistent-as-needed'],
'spaced-comment': 'error',
'array-bracket-newline': ['error', 'consistent'],
'array-bracket-spacing': 'error',
'array-element-newline': ['error', 'consistent'],
'arrow-spacing': 'error',
'block-spacing': 'error',
'brace-style': 'error',
'comma-spacing': 'error',
'comma-style': 'error',
'dot-location': ['error', 'property'],
'func-call-spacing': 'error',
'function-call-argument-newline': ['error', 'consistent'],
'key-spacing': 'error',
'keyword-spacing': 'error',
'lines-around-comment': ['error', { 'allowBlockStart': true }],
'lines-between-class-members': 'error',
'no-multi-spaces': 'error',
'no-multiple-empty-lines': ['error', { 'max': 1 }],
'no-trailing-spaces': 'error',
'no-whitespace-before-property': 'error',
'object-property-newline': ['error',
{ 'allowAllPropertiesOnSameLine': true }],
'object-curly-spacing': ['error', 'always'],
'padded-blocks': ['error', 'never'],
'padding-line-between-statements': [
'error',
{ 'blankLine': 'always', 'prev': ['block', 'multiline-block-like'], 'next': '*' },
{ 'blankLine': 'always', 'prev': 'import', 'next': '*' },
{ 'blankLine': 'any', 'prev': 'import', 'next': 'import' },
{ 'blankLine': 'always', 'prev': ['const', 'let', 'var'], 'next': '*' },
{ 'blankLine': 'any', 'prev': ['const', 'let', 'var'], 'next': ['const', 'let', 'var', 'expression', 'if'] },
],
'quotes': ['error', 'single'],
'rest-spread-spacing': 'error',
'semi': 'error',
'semi-spacing': 'error',
'semi-style': 'error',
'space-before-blocks': 'error',
'space-before-function-paren': ['error', 'never'],
'space-in-parens': 'error',
}),
});
const config =
require('../../vendor/shopware/administration/Resources/app/administration/.eslintrc');
const newRules = {
'max-len': ['error', 256, { ignoreRegExpLiterals: true }],
'sw-deprecation-rules/private-feature-declarations': 'off'
,};
const [overrideJS, ...overrideOthers] = config.overrides;
const newOverrideJS = Object.assign(overrideJS, {
rules: Object.assign(overrideJS.rules, newRules),
});
module.exports = Object.assign(config, {
rules: Object.assign(config.rules, newRules),
overrides: [newOverrideJS, ...overrideOthers],
});
vendor/shopware/storefront/Resources/app/storefront/node_modules/eslint/bin/eslint.js \
--config .eslintrc.js \
--no-error-on-unmatched-pattern \
./custom/**/Sitegeist*/src/**/storefront/src/**/*.js
vendor/shopware/administration/Resources/app/administration/node_modules/eslint/bin/eslint.js \
--config .eslintrc.admin.js \
--no-error-on-unmatched-pattern \
./custom/**/Sitegeist*/src/**/administration/src/**/*.js
Prettier
Prettier is an automatic code formatting tool designed to automate the process of code formatting and thus improve code consistency and readability. It supports a variety of programming languages such as JavaScript, TypeScript, CSS, HTML and more. Prettier analyzes the code and formats it according to the predefined rules, resulting in a consistent and clean code. As a result, it saves developers a lot of time that they would normally spend manually formatting code. Prettier can be used in many development environments such as VSCode, Atom, PhpStorm and many others and has become an important tool in the developer community.
PHP-Linting
PHP-Linting is an important part of Shopware 6 development. It helps to detect and avoid code errors and problems in PHP code at an early stage to ensure better code quality.
Our Shopware team uses the Symplify Easing Coding Standards as a guideline for the linting of our PHP code. These standards are based on PHP Code Sniffer and PHP CS Fixer and extend the known PSR standards with additional rules and recommendations. The linting tool automatically analyzes the code and issues warnings if it does not comply with the standards. This ensures that our PHP code complies with current best practices and conventions and is easier to understand and maintain.
<?php
declare(strict_types=1);
use Symfony\Component\Finder\Finder;
use Symplify\EasyCodingStandard\Config\ECSConfig;
use Symplify\EasyCodingStandard\ValueObject\Set\SetList;
return static function (ECSConfig $ecsConfig): void {
// directories for linting
$finder = Finder::create();
$finder->directories()->in(__DIR__.'/../../custom/*/Sitegeist*/');
$paths = [];
foreach ($finder as $directory) {
$paths[] = $directory->getPath();
}
$ecsConfig->paths($paths);
$ecsConfig->sets([
SetList::PSR_12,
SetList::CLEAN_CODE,
SetList::COMMON,
__DIR__ . '/sitegeist_rules.php'
]);
};
<?php
declare (strict_types=1);
return static function (ECSConfig $ecsConfig) : void {
// Symfony
$ecsConfig->ruleWithConfiguration(
ArraySyntaxFixer::class,
[
'syntax' => 'short'
]
);
$ecsConfig->rule(BinaryOperatorSpacesFixer::class);
$ecsConfig->rule(BlankLineAfterNamespaceFixer::class);
$ecsConfig->rule(BlankLineAfterOpeningTagFixer::class);
$ecsConfig->ruleWithConfiguration(
BlankLineBeforeStatementFixer::class,
[
'statements' => [
'return'
]
]
);
$ecsConfig->ruleWithConfiguration(
BracesFixer::class,
[
'allow_single_line_closure' => true
]
);
$ecsConfig->rule(CastSpacesFixer::class);
$ecsConfig->ruleWithConfiguration(
ClassAttributesSeparationFixer::class,
[
'elements' => [
'method' => 'one'
]
]
);
$ecsConfig->ruleWithConfiguration(
ClassDefinitionFixer::class,
[
'single_line' => true
]
);
$ecsConfig->rule(ConcatSpaceFixer::class);
$ecsConfig->rule(ConstantCaseFixer::class);
$ecsConfig->rule(DeclareEqualNormalizeFixer::class);
$ecsConfig->rule(ElseifFixer::class);
$ecsConfig->rule(EncodingFixer::class);
$ecsConfig->rule(FullOpeningTagFixer::class);
$ecsConfig->rule(FunctionDeclarationFixer::class);
$ecsConfig->rule(FunctionTypehintSpaceFixer::class);
$ecsConfig->rule(IncludeFixer::class);
$ecsConfig->rule(IncrementStyleFixer::class);
$ecsConfig->rule(IndentationTypeFixer::class);
$ecsConfig->rule(LineEndingFixer::class);
$ecsConfig->rule(LowercaseCastFixer::class);
$ecsConfig->rule(LowercaseKeywordsFixer::class);
$ecsConfig->rule(LowercaseStaticReferenceFixer::class);
$ecsConfig->rule(MagicConstantCasingFixer::class);
$ecsConfig->rule(MagicMethodCasingFixer::class);
$ecsConfig->rule(MethodArgumentSpaceFixer::class);
$ecsConfig->rule(NativeFunctionCasingFixer::class);
$ecsConfig->rule(NativeFunctionTypeDeclarationCasingFixer::class);
$ecsConfig->rule(NewWithBracesFixer::class);
$ecsConfig->rule(NoBlankLinesAfterClassOpeningFixer::class);
$ecsConfig->rule(NoBlankLinesAfterPhpdocFixer::class);
$ecsConfig->rule(NoBreakCommentFixer::class);
$ecsConfig->rule(NoClosingTagFixer::class);
$ecsConfig->rule(NoEmptyCommentFixer::class);
$ecsConfig->rule(NoEmptyPhpdocFixer::class);
$ecsConfig->rule(NoEmptyStatementFixer::class);
$ecsConfig->ruleWithConfiguration(
NoExtraBlankLinesFixer::class,
[
'tokens' => [
'curly_brace_block',
'extra',
'parenthesis_brace_block',
'square_brace_block',
'throw',
'use',
]
]
);
$ecsConfig->rule(NoLeadingImportSlashFixer::class);
$ecsConfig->rule(NoLeadingNamespaceWhitespaceFixer::class);
$ecsConfig->rule(NoMixedEchoPrintFixer::class);
$ecsConfig->rule(NoMultilineWhitespaceAroundDoubleArrowFixer::class);
$ecsConfig->rule(NoShortBoolCastFixer::class);
$ecsConfig->rule(NoSinglelineWhitespaceBeforeSemicolonsFixer::class);
$ecsConfig->rule(NoSpacesAfterFunctionNameFixer::class);
$ecsConfig->rule(NoSpacesAroundOffsetFixer::class);
$ecsConfig->rule(NoSpacesInsideParenthesisFixer::class);
$ecsConfig->ruleWithConfiguration(
NoSuperfluousPhpdocTagsFixer::class,
[
'allow_mixed' => true,
'allow_unused_params' => true
]
);
$ecsConfig->rule(NoTrailingWhitespaceFixer::class);
$ecsConfig->rule(NoTrailingWhitespaceInCommentFixer::class);
$ecsConfig->rule(NoUnneededControlParenthesesFixer::class);
$ecsConfig->ruleWithConfiguration(
NoUnneededCurlyBracesFixer::class,
[
'namespaces' => true
]
);
$ecsConfig->rule(NoUnusedImportsFixer::class);
$ecsConfig->rule(NoWhitespaceBeforeCommaInArrayFixer::class);
$ecsConfig->rule(NoWhitespaceInBlankLineFixer::class);
$ecsConfig->rule(NormalizeIndexBraceFixer::class);
$ecsConfig->rule(ObjectOperatorWithoutWhitespaceFixer::class);
$ecsConfig->rule(OrderedImportsFixer::class);
$ecsConfig->rule(PhpUnitFqcnAnnotationFixer::class);
$ecsConfig->ruleWithConfiguration(
PhpdocAlignFixer::class,
[
'tags' => [
'method',
'param',
'property',
'return',
'throws',
'type',
'var'
]
]
);
$ecsConfig->rule(PhpdocAnnotationWithoutDotFixer::class);
$ecsConfig->rule(PhpdocIndentFixer::class);
$ecsConfig->rule(PhpdocNoAccessFixer::class);
$ecsConfig->rule(PhpdocNoAliasTagFixer::class);
$ecsConfig->rule(PhpdocNoPackageFixer::class);
$ecsConfig->rule(PhpdocNoUselessInheritdocFixer::class);
$ecsConfig->rule(PhpdocReturnSelfReferenceFixer::class);
$ecsConfig->rule(PhpdocScalarFixer::class);
$ecsConfig->rule(PhpdocSeparationFixer::class);
$ecsConfig->rule(PhpdocSingleLineVarSpacingFixer::class);
$ecsConfig->rule(PhpdocSummaryFixer::class);
$ecsConfig->rule(PhpdocToCommentFixer::class);
$ecsConfig->rule(PhpdocTrimFixer::class);
$ecsConfig->rule(PhpdocTrimConsecutiveBlankLineSeparationFixer::class);
$ecsConfig->rule(PhpdocTypesFixer::class);
$ecsConfig->ruleWithConfiguration(
PhpdocTypesOrderFixer::class,
[
'null_adjustment' => 'always_last',
'sort_algorithm' => 'none'
]
);
$ecsConfig->rule(PhpdocVarWithoutNameFixer::class);
$ecsConfig->rule(ReturnTypeDeclarationFixer::class);
$ecsConfig->rule(SemicolonAfterInstructionFixer::class);
$ecsConfig->rule(ShortScalarCastFixer::class);
$ecsConfig->rule(SingleBlankLineAtEofFixer::class);
$ecsConfig->rule(SingleBlankLineBeforeNamespaceFixer::class);
$ecsConfig->rule(SingleClassElementPerStatementFixer::class);
$ecsConfig->rule(SingleImportPerStatementFixer::class);
$ecsConfig->rule(SingleLineAfterImportsFixer::class);
$ecsConfig->ruleWithConfiguration(
SingleLineCommentStyleFixer::class,
[
'comment_types' => ['hash']
]
);
$ecsConfig->rule(SingleLineThrowFixer::class);
$ecsConfig->rule(SingleQuoteFixer::class);
$ecsConfig->rule(SingleTraitInsertPerStatementFixer::class);
$ecsConfig->ruleWithConfiguration(
SpaceAfterSemicolonFixer::class,
[
'remove_in_empty_for_expressions' => true
]
);
$ecsConfig->rule(StandardizeIncrementFixer::class);
$ecsConfig->rule(StandardizeNotEqualsFixer::class);
$ecsConfig->rule(SwitchCaseSemicolonToColonFixer::class);
$ecsConfig->rule(SwitchCaseSpaceFixer::class);
$ecsConfig->rule(TernaryOperatorSpacesFixer::class);
$ecsConfig->ruleWithConfiguration(
TrailingCommaInMultilineFixer::class,
[
'elements' => [TrailingCommaInMultilineFixer::ELEMENTS_ARRAYS]
]
);
$ecsConfig->rule(TrimArraySpacesFixer::class);
$ecsConfig->rule(UnaryOperatorSpacesFixer::class);
$ecsConfig->rule(VisibilityRequiredFixer::class);
$ecsConfig->rule(WhitespaceAfterCommaInArrayFixer::class);
// Shopware
$ecsConfig->rule(ModernizeTypesCastingFixer::class);
$ecsConfig->rule(NativeConstantInvocationFixer::class);
$ecsConfig->rule(FopenFlagsFixer::class);
$ecsConfig->rule(NativeFunctionInvocationFixer::class);
$ecsConfig->rule(NullableTypeDeclarationForDefaultNullValueFixer::class);
$ecsConfig->rule(VoidReturnFixer::class);
$ecsConfig->rule(OperatorLinebreakFixer::class);
$ecsConfig->rule(GeneralPhpdocAnnotationRemoveFixer::class);
$ecsConfig->rule(PhpdocOrderFixer::class);
$ecsConfig->rule(NoUselessReturnFixer::class);
$ecsConfig->rule(CompactNullableTypehintFixer::class);
// sitegeist Custom
$ecsConfig->rule(NoUselessElseFixer::class);
$ecsConfig->rule(NoSuperfluousElseifFixer::class);
$ecsConfig->ruleWithConfiguration(
ForbiddenFunctionsSniff::class,
[
'forbiddenFunctions' => [
'die' => null,
'var_dump' => null,
'dd' => null
]
]
);
};
PHP Code Sniffer
PHP Code Sniffer provides a variety of predefined rule sets for different coding standards, such as PSR-1, PSR-2, PSR-12, Zend and Symfony. These rule sets include rules for indentation, use of spaces and line breaks, naming conventions for variables and functions, comments and more. PHP Code Sniffer can also be customized for specific projects or teams by creating custom rules or adapting existing rules.
PHP CS Fixer
PHP CS Fixer can be used in combination with PHP Code Sniffer to further improve the quality of the code. If the code deviates from the standards, PHP Code Sniffer issues warnings that encourage developers to correct the code accordingly. PHP CS Fixer can be used to automatically format the code to make it consistent and readable. This makes the developer's job much easier by allowing them to have the code formatted automatically and focus on development. The use of PHP CS Fixer helps to improve the overall efficiency and quality of code development.
Automatic Linting in the CI/CD pipeline
At sitegeist, we have implemented an effective method to ensure that the code is checked for errors and deviations from the defined coding standards before it is pushed to Git. To do this, we use the precommit hook, a type of script that is automatically executed before each commit. We run all relevant Frontend and PHPLinting scripts to ensure that the code remains at a consistent level at all times and errors are avoided. This saves us valuable time in troubleshooting and ensures that the quality of the code is always at the highest level.
In addition to the precommit hook, we also perform the linting in the GitLab CI/CD pipeline once again. The code is checked again for deviations from the defined coding standards and any errors or warnings are output. If the linting finds errors in the pipeline, a merge request is blocked and it is not possible to integrate the code into the main branch. This ensures that the code is error-free at all times and complies with the coding standards. In this way, errors and quality problems can be avoided, which in turn helps to ensure that our code always remains at a high level of quality.
Outlook
At sitegeist, the quality and efficiency of our work is very important to us, which is why we ensure that our linting process is always up to date. We aim to continuously improve our tools and methods to resolve issues faster and more effectively, saving time and money. By optimizing our linting process, we have more time to focus on developing individual solutions that are tailored to your specific needs. We are convinced that the continuous improvement of our linting process will help us to offer our customers the best possible service and make our company successful in the long term.