Repository: eslint/css (js)

Issue:
Change Request: PostCSS syntax support
### Environment

ESLint version: Latest (Possible to include the version on the Code Explorer?)
@eslint/css version: Latest (Possible to include the version on the Code Explorer?)
Node version: N/A
npm version: N/A
Operating System: mac


### What problem do you want to solve?

PostCSS is an extremely popular enhancement over CSS. However, since this plugin only seems to support strict CSS syntax, it cannot be leveraged.

For example, when using https://github.com/postcss/postcss-mixins, it yields a parsing error: [Code Explorer](https://explorer.eslint.org/#eslint-explorer=IntcInN0YXRlXCI6e1widG9vbFwiOlwiYXN0XCIsXCJjb2RlXCI6e1wiamF2YXNjcmlwdFwiOlwiLyoqXFxuICogVHlwZSBvciBwYXN0ZSBzb21lIEphdmFTY3JpcHQgaGVyZSB0byBsZWFybiBtb3JlIGFib3V0XFxuICogdGhlIHN0YXRpYyBhbmFseXNpcyB0aGF0IEVTTGludCBjYW4gZG8gZm9yIHlvdS5cXG4gKiBcXG4gKiBUaGUgdGhyZWUgdGFicyBhcmU6XFxuICogXFxuICogLSBBU1QgLSBUaGUgQWJzdHJhY3QgU3ludGF4IFRyZWUgb2YgdGhlIGNvZGUsIHdoaWNoIGNhblxcbiAqICAgYmUgdXNlZnVsIHRvIHVuZGVyc3RhbmQgdGhlIHN0cnVjdHVyZSBvZiB0aGUgY29kZS4gWW91XFxuICogICBjYW4gdmlldyB0aGlzIHN0cnVjdHVyZSBhcyBKU09OIG9yIGluIGEgdHJlZSBmb3JtYXQuXFxuICogLSBTY29wZSAtIFRoZSBzY29wZSBzdHJ1Y3R1cmUgb2YgdGhlIGNvZGUsIHdoaWNoIGNhbiBiZVxcbiAqICAgdXNlZnVsIHRvIHVuZGVyc3RhbmQgaG93IHZhcmlhYmxlcyBhcmUgZGVmaW5lZCBhbmRcXG4gKiAgIHdoZXJlIHRoZXkgYXJlIHVzZWQuXFxuICogLSBDb2RlIFBhdGggLSBUaGUgY29kZSBwYXRoIHN0cnVjdHVyZSBvZiB0aGUgY29kZSwgd2hpY2hcXG4gKiAgIGNhbiBiZSB1c2VmdWwgdG8gdW5kZXJzdGFuZCBob3cgdGhlIGNvZGUgaXMgZXhlY3V0ZWQuXFxuICogXFxuICogWW91IGNhbiBjaGFuZ2UgdGhlIHdheSB0aGF0IHRoZSBKYXZhU2NyaXB0IGNvZGUgaXMgaW50ZXJwcmV0ZWRcXG4gKiBieSBjbGlja2luZyBcXFwiSmF2YVNjcmlwdFxcXCIgaW4gdGhlIGhlYWRlciBhbmQgc2VsZWN0aW5nIGRpZmZlcmVudFxcbiAqIG9wdGlvbnMuXFxuICovXFxuXFxuaW1wb3J0IGpzIGZyb20gXFxcIkBlc2xpbnQvanNcXFwiO1xcblxcbmZ1bmN0aW9uIGdldENvbmZpZygpIHtcXG4gICAgcmV0dXJuIHtcXG4gICAgICAgIHJ1bGVzOiB7XFxuICAgICAgICAgICAgXFxcInByZWZlci1jb25zdFxcXCI6IFxcXCJ3YXJuXFxcIlxcbiAgICAgICAgfVxcbiAgICB9O1xcbn1cXG5cXG5leHBvcnQgZGVmYXVsdCBbXFxuICAgIC4uLmpzLmNvbmZpZ3MucmVjb21tZW5kZWQsXFxuICAgIGdldENvbmZpZygpXFxuXTtcIixcImpzb25cIjpcIi8qKlxcbiAqIFR5cGUgb3IgcGFzdGUgc29tZSBKU09OIGhlcmUgdG8gbGVhcm4gbW9yZSBhYm91dFxcbiAqIHRoZSBzdGF0aWMgYW5hbHlzaXMgdGhhdCBFU0xpbnQgY2FuIGRvIGZvciB5b3UuXFxuICpcXG4gKiBUaGUgdGFicyBhcmU6XFxuICpcXG4gKiAtIEFTVCAtIFRoZSBBYnN0cmFjdCBTeW50YXggVHJlZSBvZiB0aGUgY29kZSwgd2hpY2ggY2FuXFxuICogICBiZSB1c2VmdWwgdG8gdW5kZXJzdGFuZCB0aGUgc3RydWN0dXJlIG9mIHRoZSBjb2RlLiBZb3VcXG4gKiAgIGNhbiB2aWV3IHRoaXMgc3RydWN0dXJlIGFzIEpTT04gb3IgaW4gYSB0cmVlIGZvcm1hdC5cXG4gKlxcbiAqIFlvdSBjYW4gY2hhbmdlIHRoZSB3YXkgdGhhdCB0aGUgSlNPTiBjb2RlIGlzIGludGVycHJldGVkXFxuICogYnkgY2xpY2tpbmcgXFxcIkpTT05cXFwiIGluIHRoZSBoZWFkZXIgYW5kIHNlbGVjdGluZyBkaWZmZXJlbnRcXG4gKiBvcHRpb25zLlxcbiAqXFxuICogVGhpcyBleGFtcGxlIGlzIGluIEpTT05DIG1vZGUsIHdoaWNoIGFsbG93cyBjb21tZW50cy5cXG4gKi9cXG5cXG57XFxuICAgIFxcXCJrZXkxXFxcIjogW3RydWUsIGZhbHNlLCBudWxsXSxcXG4gICAgXFxcImtleTJcXFwiOiB7XFxuICAgICAgICBcXFwia2V5M1xcXCI6IFsxLCAyLCBcXFwiM1xcXCIsIDFlMTAsIDFlLTNdXFxuICAgIH1cXG59XCIsXCJtYXJrZG93blwiOlwiPCEtLVxcblR5cGUgb3IgcGFzdGUgc29tZSBNYXJrZG93biBoZXJlIHRvIGxlYXJuIG1vcmUgYWJvdXRcXG50aGUgc3RhdGljIGFuYWx5c2lzIHRoYXQgRVNMaW50IGNhbiBkbyBmb3IgeW91LlxcblxcblRoZSB0YWJzIGFyZTpcXG5cXG4tIEFTVCAtIFRoZSBBYnN0cmFjdCBTeW50YXggVHJlZSBvZiB0aGUgY29kZSwgd2hpY2ggY2FuXFxuYmUgdXNlZnVsIHRvIHVuZGVyc3RhbmQgdGhlIHN0cnVjdHVyZSBvZiB0aGUgY29kZS4gWW91XFxuY2FuIHZpZXcgdGhpcyBzdHJ1Y3R1cmUgYXMgSlNPTiBvciBpbiBhIHRyZWUgZm9ybWF0LlxcblxcbllvdSBjYW4gY2hhbmdlIHRoZSB3YXkgdGhhdCB0aGUgTWFya2Rvd24gY29kZSBpcyBpbnRlcnByZXRlZFxcbmJ5IGNsaWNraW5nIFxcXCJNYXJrZG93blxcXCIgaW4gdGhlIGhlYWRlciBhbmQgc2VsZWN0aW5nIGRpZmZlcmVudFxcbm9wdGlvbnMuXFxuXFxuVGhpcyBleGFtcGxlIGlzIGluIENvbW1vbk1hcmsgbW9kZS5cXG4tLT5cXG5cXG4jIEVTTGludCBNYXJrZG93biBFeGFtcGxlXFxuXFxuVGhpcyBpcyBhbiBleGFtcGxlIG9mIGEgTWFya2Rvd24gZmlsZSB0aGF0IGNhbiBiZSBwYXJzZWRcXG5ieSBFU0xpbnQuIE1hcmtkb3duIGlzIGEgc2ltcGxlIG1hcmt1cCBsYW5ndWFnZSB0aGF0IGlzXFxub2Z0ZW4gdXNlZCBmb3IgZG9jdW1lbnRhdGlvbi5cXG5cXG4jIyBGZWF0dXJlc1xcblxcbi0gTWFrZSB0aGluZ3MgKml0YWxpYyosICoqYm9sZCoqLCBvciBgY29kZWBcXG4tIENyZWF0ZSBbbGlua3NdKGh0dHBzOi8vZXNsaW50Lm9yZylcXG4tIFN1cHBvcnRzIEhUTUwgPHNwYW4gc3R5bGU9XFxcImNvbG9yOiByZWQ7XFxcIj5lbGVtZW50czwvc3Bhbj5cXG4tIExpc3RzXFxuICAtIE5lc3RlZCBsaXN0c1wiLFwiY3NzXCI6XCJAZGVmaW5lLW1peGluIGljb24gJG5ldHdvcmssICRjb2xvcjogYmx1ZSB7XFxuICAgIC5pY29uLmlzLSQobmV0d29yaykge1xcbiAgICAgICAgY29sb3I6ICRjb2xvcjtcXG4gICAgICAgIEBtaXhpbi1jb250ZW50O1xcbiAgICB9XFxuICAgIC5pY29uLmlzLSQobmV0d29yayk6aG92ZXIge1xcbiAgICAgICAgY29sb3I6IHdoaXRlO1xcbiAgICAgICAgYmFja2dyb3VuZDogJGNvbG9yO1xcbiAgICB9XFxufVxcblxcbkBtaXhpbiBpY29uIHR3aXR0ZXIge1xcbiAgICBiYWNrZ3JvdW5kOiB1cmwodHd0LnBuZyk7XFxufVxcbkBtaXhpbiBpY29uIHlvdXR1YmUsIHJlZCB7XFxuICAgIGJhY2tncm91bmQ6IHVybCh5b3V0dWJlLnBuZyk7XFxufVwifSxcImxhbmd1YWdlXCI6XCJjc3NcIixcImpzT3B0aW9uc1wiOntcInBhcnNlclwiOlwiZXNwcmVlXCIsXCJzb3VyY2VUeXBlXCI6XCJtb2R1bGVcIixcImVzVmVyc2lvblwiOlwibGF0ZXN0XCIsXCJpc0pTWFwiOnRydWV9LFwianNvbk9wdGlvbnNcIjp7XCJqc29uTW9kZVwiOlwianNvbmNcIn0sXCJjc3NPcHRpb25zXCI6e1wiY3NzTW9kZVwiOlwiY3NzXCJ9LFwibWFya2Rvd25PcHRpb25zXCI6e1wibWFya2Rvd25Nb2RlXCI6XCJjb21tb25tYXJrXCJ9LFwid3JhcFwiOnRydWUsXCJ2aWV3TW9kZXNcIjp7XCJhc3RWaWV3XCI6XCJ0cmVlXCIsXCJzY29wZVZpZXdcIjpcImZsYXRcIixcInBhdGhWaWV3XCI6XCJncmFwaFwifSxcInBhdGhJbmRleFwiOntcImluZGV4XCI6MCxcImluZGV4ZXNcIjoyfX0sXCJ2ZXJzaW9uXCI6MH0i)

```css
@define-mixin icon $network, $color: blue {
    .icon.is-$(network) {
        color: $color;
        @mixin-content;
    }
    .icon.is-$(network):hover {
        color: white;
        background: $color;
    }
}

@mixin icon twitter {
    background: url(twt.png);
}
@mixin icon youtube, red {
    background: url(youtube.png);
}
```

This emits the error:
```
Error: Semicolon or block is expected(column 20)
```

### What do you think is the correct solution?

Some ideas: 
- Looser syntax parsing, maybe behind an option?
- PostCSS plugin

### Participation

- [ ] I am willing to submit a pull request for this change.

### Additional comments

_No response_

Pull request description:
feat: Add tolerant parsing mode
<!--
    Thank you for contributing!

    ESLint adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct).
-->

#### Prerequisites checklist

- [x] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/HEAD/CONTRIBUTING.md).

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/latest/contribute/pull-requests)
    - Update or create tests
    - If performance-related, include a benchmark
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

#### What is the purpose of this pull request?

Add a tolerant parsing mode.

#### What changes did you make? (Give an overview)

- Updated `CSSLanguage` to contain `defaultLanguageOptions` and `validateLanguageOptions()`
- Updated `CSSLanguage#parse()` to accept language options
- Updated tests
- Upgraded `@eslint/core` to v0.7.0 (not the latest, for best compatibility)

#### Related Issues

fixes #29


<!-- include tags like "fixes #123" or "refs #123" -->

#### Is there anything you'd like reviewers to focus on?

<!-- markdownlint-disable-file MD004 -->


Patch:
diff --git a/README.md b/README.md
index f6566be..aff2bf4 100644
--- a/README.md
+++ b/README.md
@@ -132,6 +132,31 @@ export default [
 ];
 ```
 
+By default, the CSS parser runs in strict mode, which reports all parsing errors. If you'd like to allow recoverable parsing errors (those that the browser automatically fixes on its own), you can set the `tolerant` option to `true`:
+
+```js
+// eslint.config.js
+import css from "@eslint/css";
+
+export default [
+	{
+		files: ["**/*.css"],
+		plugins: {
+			css,
+		},
+		language: "css/css",
+		languageOptions: {
+			tolerant: true,
+		},
+		rules: {
+			"css/no-empty-blocks": "error",
+		},
+	},
+];
+```
+
+Setting `tolerant` to `true` is necessary if you are using custom syntax, such as [PostCSS](https://postcss.org/) plugins, that aren't part of the standard CSS syntax.
+
 ## License
 
 Apache 2.0
@@ -147,8 +172,8 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
 <h3>Platinum Sponsors</h3>
 <p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="128"></a></p><h3>Gold Sponsors</h3>
 <p><a href="https://trunk.io/"><img src="https://images.opencollective.com/trunkio/fb92d60/avatar.png" alt="trunk.io" height="96"></a></p><h3>Silver Sponsors</h3>
-<p><a href="https://www.serptriumph.com/"><img src="https://images.opencollective.com/serp-triumph5/fea3074/logo.png" alt="SERP Triumph" height="64"></a> <a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/fe76f99/logo.png" alt="JetBrains" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://www.wordhint.net/"><img src="https://images.opencollective.com/wordhint/be86813/avatar.png" alt="WordHint" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340?v=4" alt="GitBook" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465?v=4" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a></p>
+<p><a href="https://www.serptriumph.com/"><img src="https://images.opencollective.com/serp-triumph5/fea3074/logo.png" alt="SERP Triumph" height="64"></a> <a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/fe76f99/logo.png" alt="JetBrains" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301" alt="American Express" height="64"></a> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
+<p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104" alt="Nx" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a></p>
 <h3>Technology Sponsors</h3>
 Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work.
 <p><a href="https://netlify.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/netlify-icon.svg" alt="Netlify" height="32"></a> <a href="https://algolia.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/algolia-icon.svg" alt="Algolia" height="32"></a> <a href="https://1password.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/1password-icon.svg" alt="1Password" height="32"></a></p>
diff --git a/package.json b/package.json
index a88afb8..044965f 100644
--- a/package.json
+++ b/package.json
@@ -73,7 +73,7 @@
     "css-tree": "^3.0.1"
   },
   "devDependencies": {
-    "@eslint/core": "^0.6.0",
+    "@eslint/core": "^0.7.0",
     "@eslint/json": "^0.5.0",
     "@types/eslint": "^8.56.10",
     "c8": "^9.1.0",
diff --git a/src/languages/css-language.js b/src/languages/css-language.js
index f8165b0..79b4096 100644
--- a/src/languages/css-language.js
+++ b/src/languages/css-language.js
@@ -25,6 +25,11 @@ import { visitorKeys } from "./css-visitor-keys.js";
 /** @typedef {import("@eslint/core").File} File */
 /** @typedef {import("@eslint/core").FileError} FileError */
 
+/**
+ * @typedef {Object} CSSLanguageOptions
+ * @property {boolean} [tolerant] Whether to be tolerant of recoverable parsing errors.
+ */
+
 //-----------------------------------------------------------------------------
 // Exports
 //-----------------------------------------------------------------------------
@@ -64,21 +69,38 @@ export class CSSLanguage {
 	 */
 	visitorKeys = visitorKeys;
 
+	/**
+	 * The default language options.
+	 * @type {CSSLanguageOptions}
+	 */
+	defaultLanguageOptions = {
+		tolerant: false,
+	};
+
 	/**
 	 * Validates the language options.
-	 * @returns {void}
+	 * @param {CSSLanguageOptions} languageOptions The language options to validate.
 	 * @throws {Error} When the language options are invalid.
 	 */
-	validateLanguageOptions() {
-		// noop
+	validateLanguageOptions(languageOptions) {
+		if (
+			"tolerant" in languageOptions &&
+			typeof languageOptions.tolerant !== "boolean"
+		) {
+			throw new TypeError(
+				"Expected a boolean value for 'tolerant' option.",
+			);
+		}
 	}
 
 	/**
 	 * Parses the given file into an AST.
 	 * @param {File} file The virtual file to parse.
+	 * @param {Object} [context] The parsing context.
+	 * @param {CSSLanguageOptions} [context.languageOptions] The language options to use for parsing.
 	 * @returns {ParseResult} The result of parsing.
 	 */
-	parse(file) {
+	parse(file, { languageOptions = {} } = {}) {
 		// Note: BOM already removed
 		const text = /** @type {string} */ (file.body);
 
@@ -88,6 +110,8 @@ export class CSSLanguage {
 		/** @type {FileError[]} */
 		const errors = [];
 
+		const { tolerant } = languageOptions;
+
 		/*
 		 * Check for parsing errors first. If there's a parsing error, nothing
 		 * else can happen. However, a parsing error does not throw an error
@@ -107,8 +131,10 @@ export class CSSLanguage {
 						});
 					},
 					onParseError(error) {
-						// @ts-ignore -- types are incorrect
-						errors.push(error);
+						if (!tolerant) {
+							// @ts-ignore -- types are incorrect
+							errors.push(error);
+						}
 					},
 				}),
 			);


Test patch:
diff --git a/tests/languages/css-language.test.js b/tests/languages/css-language.test.js
index 8c405a0..bc96a0b 100644
--- a/tests/languages/css-language.test.js
+++ b/tests/languages/css-language.test.js
@@ -38,7 +38,7 @@ describe("CSSLanguage", () => {
 			assert.strictEqual(result.ast.children[0].type, "Rule");
 		});
 
-		it("should return an error when parsing invalid CSS", () => {
+		it("should return an error when CSS has a recoverable error", () => {
 			const language = new CSSLanguage();
 			const result = language.parse({
 				body: "a { foo; bar: 1! }",
@@ -61,6 +61,19 @@ describe("CSSLanguage", () => {
 			assert.strictEqual(result.errors[1].column, 18);
 		});
 
+		it("should not return an error when CSS has a recoverable error and tolerant: true is used", () => {
+			const language = new CSSLanguage();
+			const result = language.parse(
+				{
+					body: "a { foo; bar: 1! }",
+					path: "test.css",
+				},
+				{ languageOptions: { tolerant: true } },
+			);
+
+			assert.strictEqual(result.ok, true);
+		});
+
 		// https://github.com/csstree/csstree/issues/301
 		it.skip("should return an error when EOF is discovered before block close", () => {
 			const language = new CSSLanguage();
diff --git a/tests/plugin/eslint.test.js b/tests/plugin/eslint.test.js
index dbfd2eb..02ae869 100644
--- a/tests/plugin/eslint.test.js
+++ b/tests/plugin/eslint.test.js
@@ -37,6 +37,72 @@ describe("Plugin", () => {
 		});
 	});
 
+	describe("languageOptions", () => {
+		const config = {
+			files: ["*.css"],
+			plugins: {
+				css,
+			},
+			language: "css/css",
+			rules: {
+				"css/no-empty-blocks": "error",
+			},
+		};
+
+		describe("tolerant", () => {
+			it("should not report a parsing error when CSS has a recoverable error and tolerant: true is used", async () => {
+				const code = "a { foo; bar: 1! }";
+
+				const eslint = new ESLint({
+					overrideConfigFile: true,
+					overrideConfig: {
+						...config,
+						languageOptions: {
+							tolerant: true,
+						},
+					},
+				});
+
+				const results = await eslint.lintText(code, {
+					filePath: "test.css",
+				});
+
+				assert.strictEqual(results.length, 1);
+				assert.strictEqual(results[0].messages.length, 0);
+			});
+
+			it("should report a parsing error when CSS has a recoverable error and tolerant is undefined", async () => {
+				const code = "a { foo; bar: 1! }";
+
+				const eslint = new ESLint({
+					overrideConfigFile: true,
+					overrideConfig: config,
+				});
+
+				const results = await eslint.lintText(code, {
+					filePath: "test.css",
+				});
+
+				assert.strictEqual(results.length, 1);
+				assert.strictEqual(results[0].messages.length, 2);
+
+				assert.strictEqual(
+					results[0].messages[0].message,
+					"Parsing error: Colon is expected",
+				);
+				assert.strictEqual(results[0].messages[0].line, 1);
+				assert.strictEqual(results[0].messages[0].column, 8);
+
+				assert.strictEqual(
+					results[0].messages[1].message,
+					"Parsing error: Identifier is expected",
+				);
+				assert.strictEqual(results[0].messages[1].line, 1);
+				assert.strictEqual(results[0].messages[1].column, 18);
+			});
+		});
+	});
+
 	describe("Configuration Comments", () => {
 		const config = {
 			files: ["*.css"],