-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(json_analyze): implement noTopLevelLiterals #9367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| "@biomejs/biome": patch | ||
| --- | ||
|
|
||
| Added the nursery rule [`noTopLevelLiterals`](https://biomejs.dev/linter/rules/no-top-level-literals/). It requires the root-level value to be an array or object. | ||
|
|
||
| **Invalid:** | ||
|
|
||
| ```json | ||
| "just a string" | ||
| ``` |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| use biome_analyze::{ | ||
| Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, | ||
| }; | ||
| use biome_console::markup; | ||
| use biome_json_syntax::{AnyJsonValue, JsonRoot}; | ||
| use biome_rowan::AstNode; | ||
| use biome_rule_options::no_top_level_literals::NoTopLevelLiteralsOptions; | ||
|
|
||
| declare_lint_rule! { | ||
| /// Require the JSON top-level value to be an array or object. | ||
| /// | ||
| /// The JSON specification technically allows any JSON value (object, array, string, number, boolean, or null) to be used as the top-level element of a JSON document. | ||
| /// However, some older JSON parsers, especially those created before [RFC 7158](https://datatracker.ietf.org/doc/html/rfc7158)/[4627](https://datatracker.ietf.org/doc/html/rfc4627) was fully adopted, only support objects or arrays as the root element. | ||
| /// | ||
| /// Additionally, some security practices (such as those preventing JSON hijacking attacks) rely on the assumption that the top-level value is an object or array. | ||
| /// Using an object or array at the top level also provides better extensibility for your data structures over time. | ||
| /// | ||
| /// ## Examples | ||
| /// | ||
| /// ### Invalid | ||
| /// | ||
| /// ```json,expect_diagnostic | ||
| /// "just a string" | ||
| /// ``` | ||
| /// | ||
| /// ```json,expect_diagnostic | ||
| /// 42 | ||
| /// ``` | ||
| /// | ||
| /// ```json,expect_diagnostic | ||
| /// true | ||
| /// ``` | ||
| /// | ||
| /// ```json,expect_diagnostic | ||
| /// null | ||
| /// ``` | ||
| /// | ||
| /// ### Valid | ||
| /// | ||
| /// ```json | ||
| /// { | ||
| /// "property": "value", | ||
| /// "otherProperty": 123 | ||
| /// } | ||
| /// ``` | ||
| /// | ||
| /// ```json | ||
| /// ["element", "anotherElement"] | ||
| /// ``` | ||
| /// | ||
| /// ```json | ||
| /// {} | ||
| /// ``` | ||
| /// | ||
| /// ```json | ||
| /// [] | ||
| /// ``` | ||
| /// | ||
| pub NoTopLevelLiterals { | ||
| version: "next", | ||
| name: "noTopLevelLiterals", | ||
| language: "json", | ||
| recommended: false, | ||
| sources: &[RuleSource::EslintJson("top-level-interop").same()], | ||
| } | ||
| } | ||
|
|
||
| impl Rule for NoTopLevelLiterals { | ||
| type Query = Ast<JsonRoot>; | ||
| type State = (); | ||
| type Signals = Option<Self::State>; | ||
| type Options = NoTopLevelLiteralsOptions; | ||
|
|
||
| fn run(ctx: &RuleContext<Self>) -> Self::Signals { | ||
| let node = ctx.query(); | ||
| let value = node.value().ok()?; | ||
|
|
||
| match value { | ||
| AnyJsonValue::JsonObjectValue(_) => None, | ||
| AnyJsonValue::JsonArrayValue(_) => None, | ||
| _ => Some(()), | ||
| } | ||
| } | ||
|
|
||
| fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> { | ||
| let span = ctx.query().range(); | ||
| Some( | ||
| RuleDiagnostic::new( | ||
| rule_category!(), | ||
| span, | ||
| markup! { | ||
| "Expected the top-level value to be an array or object." | ||
| }, | ||
| ) | ||
| .note(markup! { | ||
| "Some JSON parsers only support objects or arrays as the root element. Wrap your value in an array or object to ensure compatibility." | ||
| }), | ||
| ) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| --- | ||
| source: crates/biome_json_analyze/tests/spec_tests.rs | ||
| expression: boolean.json | ||
| --- | ||
| # Input | ||
| ```json | ||
| true | ||
|
|
||
| ``` | ||
|
|
||
| # Diagnostics | ||
| ``` | ||
| boolean.json:1:1 lint/nursery/noTopLevelLiterals ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
|
|
||
| i Expected the top-level value to be an array or object. | ||
|
|
||
| > 1 │ true | ||
| │ ^^^^ | ||
| 2 │ | ||
|
|
||
| i Some JSON parsers only support objects or arrays as the root element. Wrap your value in an array or object to ensure compatibility. | ||
|
|
||
| i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. | ||
|
|
||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| null |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| --- | ||
| source: crates/biome_json_analyze/tests/spec_tests.rs | ||
| expression: null.json | ||
| --- | ||
| # Input | ||
| ```json | ||
| null | ||
|
|
||
| ``` | ||
|
|
||
| # Diagnostics | ||
| ``` | ||
| null.json:1:1 lint/nursery/noTopLevelLiterals ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
|
|
||
| i Expected the top-level value to be an array or object. | ||
|
|
||
| > 1 │ null | ||
| │ ^^^^ | ||
| 2 │ | ||
|
|
||
| i Some JSON parsers only support objects or arrays as the root element. Wrap your value in an array or object to ensure compatibility. | ||
|
|
||
| i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. | ||
|
|
||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 42 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| --- | ||
| source: crates/biome_json_analyze/tests/spec_tests.rs | ||
| expression: number.json | ||
| --- | ||
| # Input | ||
| ```json | ||
| 42 | ||
|
|
||
| ``` | ||
|
|
||
| # Diagnostics | ||
| ``` | ||
| number.json:1:1 lint/nursery/noTopLevelLiterals ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
|
|
||
| i Expected the top-level value to be an array or object. | ||
|
|
||
| > 1 │ 42 | ||
| │ ^^ | ||
| 2 │ | ||
|
|
||
| i Some JSON parsers only support objects or arrays as the root element. Wrap your value in an array or object to ensure compatibility. | ||
|
|
||
| i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. | ||
|
|
||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| "just a string" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| --- | ||
| source: crates/biome_json_analyze/tests/spec_tests.rs | ||
| expression: string.json | ||
| --- | ||
| # Input | ||
| ```json | ||
| "just a string" | ||
|
|
||
| ``` | ||
|
|
||
| # Diagnostics | ||
| ``` | ||
| string.json:1:1 lint/nursery/noTopLevelLiterals ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
|
|
||
| i Expected the top-level value to be an array or object. | ||
|
|
||
| > 1 │ "just a string" | ||
| │ ^^^^^^^^^^^^^^^ | ||
| 2 │ | ||
|
|
||
| i Some JSON parsers only support objects or arrays as the root element. Wrap your value in an array or object to ensure compatibility. | ||
|
|
||
| i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. | ||
|
|
||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| [ | ||
| "one", | ||
| "two", | ||
| "three" | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| --- | ||
| source: crates/biome_json_analyze/tests/spec_tests.rs | ||
| expression: array.json | ||
| --- | ||
| # Input | ||
| ```json | ||
| [ | ||
| "one", | ||
| "two", | ||
| "three" | ||
| ] | ||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| --- | ||
| source: crates/biome_json_analyze/tests/spec_tests.rs | ||
| expression: empty-array.json | ||
| --- | ||
| # Input | ||
| ```json | ||
| [] | ||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| --- | ||
| source: crates/biome_json_analyze/tests/spec_tests.rs | ||
| expression: empty-object.json | ||
| --- | ||
| # Input | ||
| ```json | ||
| {} | ||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "one": "value", | ||
| "two": "value", | ||
| "three": "value" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| --- | ||
| source: crates/biome_json_analyze/tests/spec_tests.rs | ||
| expression: object.json | ||
| --- | ||
| # Input | ||
| ```json | ||
| { | ||
| "one": "value", | ||
| "two": "value", | ||
| "three": "value" | ||
| } | ||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,6 @@ | ||||||||
| use biome_deserialize_macros::{Deserializable, Merge}; | ||||||||
| use serde::{Deserialize, Serialize}; | ||||||||
| #[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] | ||||||||
| #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] | ||||||||
| #[serde(rename_all = "camelCase", deny_unknown_fields, default)] | ||||||||
| pub struct NoTopLevelLiteralsOptions {} | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Add rustdoc for the options type.
Suggested patch+/// Options for the `noTopLevelLiterals` rule.
pub struct NoTopLevelLiteralsOptions {}As per coding guidelines, "**/*.rs: Use inline rustdoc documentation for rules, assists, and their options". 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the diagnostic could be improved. rule pillars.