Skip to content

Instantly share code, notes, and snippets.

@pyrsmk
Last active January 4, 2022 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pyrsmk/464f366de8dab22ac27d2d3ff860b668 to your computer and use it in GitHub Desktop.
Save pyrsmk/464f366de8dab22ac27d2d3ff860b668 to your computer and use it in GitHub Desktop.
// Currently, the ESLint plugin used by Svelte is buggy and choke on files with Sass.
// This little implementation takes care of removing styling from Svelte components as
// as well as handle any type of files other than Svelte, with complete respect of rules
// from the base ESLint plugin (that is, `.eslintrc.cjs` rules are used). It has been
// thought to be simple to use and integrate.
//
// https://github.com/sveltejs/eslint-plugin-svelte3/issues/10
//
// Usage example:
//
// node svelte-lint.js 'src/**/*'
//
// We can use STDIN too! For example, we can lint only staged files:
//
// git --no-pager diff --name-only --cached | node svelte-lint.js
import { argv } from 'process'
import { ESLint } from 'eslint'
import { FileEnumerator } from 'eslint/lib/cli-engine/file-enumerator.js'
import fs from 'fs'
import path from 'path'
const lint = async (globs) => {
let problemsInFiles = false
const supportedExtensions = [ '.js', '.ts', '.svelte' ]
const eslint = new ESLint()
const fileEnumerator = new FileEnumerator()
// We could have pass the globs directly to `iterateFiles`, but if no files are found
// it throws a NoFilesFound error. So we need to keep that error silent to avoid to
// break the linting process.
await Promise.all(
globs.map(async glob => {
try {
// Disable some rules for Svelte files.
for (const { filePath } of fileEnumerator.iterateFiles(glob)) {
if (!supportedExtensions.includes(path.extname(filePath))) {
return
}
let contents = fs.readFileSync(filePath, 'utf8')
// The magic happens here. If it's a Svelte file we remove the style tag.
if (filePath.endsWith('.svelte')) {
contents = contents.replace(/<style.*?<\/style>/gs, '')
}
const results = await eslint.lintText(contents, { filePath })
const formatter = await eslint.loadFormatter('stylish')
const report = formatter.format(results)
if (report.length > 0) {
problemsInFiles = true
console.log(report)
}
}
} catch (error) {
if (error.constructor.name == 'NoFilesFoundError') {
return
}
throw error
}
})
)
// Problems have been found, let's throw an error so the process exits with a proper
// code.
if (problemsInFiles) {
throw new Error()
}
}
// Take glob arguments and merge them with paths passed via STDIN.
let globs = argv.slice(2)
try {
const stdin = fs.readFileSync(0)
globs = globs.concat(
stdin.toString().split('\n').filter(path => path != '')
)
} catch (e) {}
lint(globs).catch(error => {
process.exitCode = 1
// When there are problems in files we throw an error with no message. Hence, we don't
// want to display it.
if (error.message) {
console.error(error)
}
})
@Perturbatio
Copy link

Hi, thanks for writing this, I modified the regex to be global so that multiple style blocks can be replaced.

/<style.*?<\/style>/gs

(since svelte files can contain multiple style blocks, e.g. styles in svelte:head vs styles for the component itself)

@pyrsmk
Copy link
Author

pyrsmk commented Oct 13, 2021

I wasn't aware of that, thanks! I'm replacing it.

@pyrsmk
Copy link
Author

pyrsmk commented Oct 14, 2021

@Perturbatio Just letting you know that I added a way to parse files passed from STDIN (very useful for only parse modified/staged files, for example).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment