') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - SymphonyOSF/sonar-css-plugin: SonarQube CSS / SCSS / Less Analyzer · GitHub
Skip to content
This repository was archived by the owner on Feb 2, 2026. It is now read-only.

Repository files navigation

SonarQube CSS / SCSS / Less Analyzer

Build StatusBuild statusQuality Gate status

Description

This SonarQube plugin analyzes:

and:

  • Computes metrics: lines of code, complexity, number of rules, etc.
  • Validates your CSS code
  • Checks for duplicated code
  • Checks various guidelines to find out code smells and potential bugs through more than:
  • Provides the ability to write your own checks

SonarQube CSS / SCSS / Less Analyzer in Action

Usage

  1. Download and install SonarQube
  2. Install the CSS / SCSS /Less analyzer either by a direct download or through the update center.
  3. Install your favorite analyzer (SonarQube Scanner, Maven, Ant, etc.) and analyze your code. Note that starting at version 2.0, Java 8 is required to run an analysis.

Plugin versions and compatibility with SonarQube versions: http://docs.sonarqube.org/display/PLUG/Plugin+Version+Matrix

Analyzing CSS code embedded in HTML/XHTML files

The plugin analyzes CSS code embedded in <style type="text/css">...</style> tags in HTML/XHTML files. To do so, as a prerequisite, SonarQube has to import those files. Either:

The list of files containing embedded CSS to analyze can be customized through the sonar.css.embedded.file.suffixes property.

stylelint / SonarQube Rule Mapping

If you are already using stylelint, adding SonarQube to your stack will help you bring code quality to another level. The stylelint / SonarQube rule mapping may be of great help to define your SonarQube quality profile.

Custom Checks

You're thinking of new valuable checks? Version 2.1 or greater provides an API to write your own custom checks. A sample plugin with detailed explanations is available here. If your custom checks may benefit the community, feel free to create a pull request in order to make the check available in the CSS / SCSS / Less analyzer.

You're thinking of new checks that may benefit the community but don't have the time or the skills to write them? Feel free to create an issue for your checks to be taken under consideration.

Metrics

Functions

Number of rules.

Complexity

The following elements increment the complexity by one:

  • Class selector
  • ID selector
  • Attribute selector
  • Type selector
  • Pseudo selector
  • Keyframes selector
  • At-rule

Complexity/function

It computes the complexity/rule, meaning the average number of selectors per rule. It gives a measurement on how specific the selectors are.

Contributing

Any contribution is more than welcome!

You feel like:

  • Adding a new check? Just open an issue to discuss the value of your check. Once validated, code, don't forget to add a lot of unit tests and open a PR.
  • Maintaining the stylelint / SonarQube rule mapping? Just open a PR
  • Fixing some bugs or improving existing checks? Just open a PR.

About

SonarQube CSS / SCSS / Less Analyzer

Topics

Resources

Stars

1 star

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages