on master help sign in
Keep @grid, drop the grid attribute
b469524iko
DESIGN.md+1
@@ -466,6 +466,7 @@ removed form with its replacement.
466| `@fn`, `@define`, `@mixin`, `@component` | `@let` (function, bundle; `@style` in a function body is scoped) |
466| `@fn`, `@define`, `@mixin`, `@component` | `@let` (function, bundle; `@style` in a function body is scoped) |
467| `@unless COND` | `@if not COND` |
467| `@unless COND` | `@if not COND` |
468| `@for $i in A..B`, `@repeat N` | `@each $i in A..B`, `@each $_ in 1..N` |
468| `@for $i in A..B`, `@repeat N` | `@each $i in A..B`, `@each $_ in 1..N` |
469+| `grid` (attribute) | `@grid`, or `display grid` |
469| `@switch`, `@match` | `@if $x == a` / `@else if` / `@else` |
470| `@switch`, `@match` | `@if $x == a` / `@else if` / `@else` |
470| `@use "file" names`, `@import file` | `@include file` |
471| `@use "file" names`, `@import file` | `@include file` |
471| `@collection`, `@env`, `@translations`, `@fetch` | `@data $name SOURCE` (glob, `env:NAME`, or a JSON file per locale) |
472| `@collection`, `@env`, `@translations`, `@fetch` | `@data $name SOURCE` (glob, `env:NAME`, or a JSON file per locale) |
crates/htmlang-core/src/codegen.rs−3
@@ -1973,9 +1973,6 @@ fn attrs_to_css(
1973 "wrap" => push_css(&mut css, "flex-wrap", "wrap"),
1973 "wrap" => push_css(&mut css, "flex-wrap", "wrap"),
1974
1974
1975 // Grid
1975 // Grid
1976− "grid" => {
1977− push_css(&mut css, "display", "grid");
1978− }
1979 "grid-cols" => {
1976 "grid-cols" => {
1980 if let Some(v) = val {
1977 if let Some(v) = val {
1981 if let Ok(n) = v.parse::<u32>() {
1978 if let Ok(n) = v.parse::<u32>() {
crates/htmlang-core/src/parser.rs+17−13
@@ -2324,6 +2324,7 @@ fn removed_attribute_hint(name: &str) -> Option<&'static str> {
2324 "backdrop-blur" => Some("use `backdrop-filter blur(...)`"),
2324 "backdrop-blur" => Some("use `backdrop-filter blur(...)`"),
2325 "truncate" => Some("use the `$truncate` bundle"),
2325 "truncate" => Some("use the `$truncate` bundle"),
2326 "critical" => Some("remove it"),
2326 "critical" => Some("remove it"),
2327+ "grid" => Some("use `@grid`, or `display grid`"),
2327 _ => None,
2328 _ => None,
2328 }
2329 }
2329}
2330}
@@ -3038,7 +3039,22 @@ fn parse_attr_list(
3038 let base_key = crate::vocab::base_attribute(attr.key.as_str());
3039 let base_key = crate::vocab::base_attribute(attr.key.as_str());
3039 let is_boolean_html =
3040 let is_boolean_html =
3040 attr.value.is_none() && crate::vocab::BOOLEAN_HTML_ATTRS.contains(&base_key);
3041 attr.value.is_none() && crate::vocab::BOOLEAN_HTML_ATTRS.contains(&base_key);
3042+ // A removed attribute that shares its name with a CSS property
3043+ // (bare `grid`) still gets its hint.
3044+ let removed = removed_attribute_hint(base_key)
3045+ .filter(|_| attr.value.is_none() || !crate::vocab::is_css_property(base_key));
3046+ if let Some(hint) = removed {
3047+ ctx.diagnostics.push(Diagnostic {
3048+ line: line_num,
3049+ column: None,
3050+ message: format!(
3051+ "attribute '{}' was removed: {} (run `htmlang upgrade`)",
3052+ base_key, hint
3053+ ),
3054+ severity: Severity::Warning,
3055+ source_line: None,
3056+ });
3041− if is_boolean_html || crate::vocab::is_style_attribute(base_key) {
3057+ } else if is_boolean_html || crate::vocab::is_style_attribute(base_key) {
3042 validate_attr_value(&attr, line_num, ctx);
3058 validate_attr_value(&attr, line_num, ctx);
3043 } else if crate::vocab::HTML_ATTRIBUTES.contains(&base_key)
3059 } else if crate::vocab::HTML_ATTRIBUTES.contains(&base_key)
3044 || base_key.starts_with("aria-")
3060 || base_key.starts_with("aria-")
@@ -3056,17 +3072,6 @@ fn parse_attr_list(
3056 severity: Severity::Warning,
3072 severity: Severity::Warning,
3057 source_line: None,
3073 source_line: None,
3058 });
3074 });
3059− } else if let Some(hint) = removed_attribute_hint(base_key) {
3060− ctx.diagnostics.push(Diagnostic {
3061− line: line_num,
3062− column: None,
3063− message: format!(
3064− "attribute '{}' was removed: {} (run `htmlang upgrade`)",
3065− base_key, hint
3066− ),
3067− severity: Severity::Warning,
3068− source_line: None,
3069− });
3070 } else {
3075 } else {
3071 let suggestion = suggest_closest(base_key, &crate::vocab::all_attributes());
3076 let suggestion = suggest_closest(base_key, &crate::vocab::all_attributes());
3072 let msg = match suggestion {
3077 let msg = match suggestion {
@@ -3356,7 +3361,6 @@ const CONTAINER_ONLY_ATTRS: &[&str] = &[
3356 "spacing",
3361 "spacing",
3357 "gap",
3362 "gap",
3358 "wrap",
3363 "wrap",
3359− "grid",
3360 "grid-cols",
3364 "grid-cols",
3361 "grid-rows",
3365 "grid-rows",
3362 "container",
3366 "container",
editors/vscode/snippets/htmlang.json+1−1
@@ -111,7 +111,7 @@
111 "Grid with named areas": {
111 "Grid with named areas": {
112 "prefix": "@grid-areas",
112 "prefix": "@grid-areas",
113 "body": [
113 "body": [
114− "@el [grid, grid-template-areas \"${1:header header}\" \"${2:sidebar main}\" \"${3:footer footer}\", gap ${4:20}]",
114+ "@grid [grid-template-areas \"${1:header header}\" \"${2:sidebar main}\" \"${3:footer footer}\", gap ${4:20}]",
115 " @el [grid-area ${5:header}]",
115 " @el [grid-area ${5:header}]",
116 " ${6:Header}",
116 " ${6:Header}",
117 " @el [grid-area ${7:sidebar}]",
117 " @el [grid-area ${7:sidebar}]",
examples/demo.hl+1−1
@@ -280,7 +280,7 @@
280
280
281 -- Implicit @el via bare [attrs] line
281 -- Implicit @el via bare [attrs] line
282 [
282 [
283− grid,
283+ display grid,
284 grid-template-columns 1fr 2fr,
284 grid-template-columns 1fr 2fr,
285 grid-template-areas "h h" "s m" "f f",
285 grid-template-areas "h h" "s m" "f f",
286 gap $base,
286 gap $base,
src/upgrade.rs+2
@@ -876,6 +876,7 @@ fn rewrite_attr_list(list: &str, element_attrs: bool) -> String {
876 // Built-in style attributes that became standard-library bundles
876 // Built-in style attributes that became standard-library bundles
877 let body = match body.trim_end() {
877 let body = match body.trim_end() {
878 "skeleton" | "no-scrollbar" | "truncate" => format!("${}", body),
878 "skeleton" | "no-scrollbar" | "truncate" => format!("${}", body),
879+ "grid" => "display grid".to_string(),
879 _ => body,
880 _ => body,
880 };
881 };
881 // `blur N` / `backdrop-blur N` → the filter they generated
882 // `blur N` / `backdrop-blur N` → the filter they generated
@@ -1336,6 +1337,7 @@ mod tests {
1336 "@el [column-gap 4, hover:box-shadow 0 1px red, filter blur(4px), backdrop-filter blur(2px), $truncate]"
1337 "@el [column-gap 4, hover:box-shadow 0 1px red, filter blur(4px), backdrop-filter blur(2px), $truncate]"
1337 );
1338 );
1338 assert_eq!(up("@el [critical, padding 4]"), "@el [padding 4]");
1339 assert_eq!(up("@el [critical, padding 4]"), "@el [padding 4]");
1340+ assert_eq!(up("@el [grid, grid-cols 3]"), "@el [display grid, grid-cols 3]");
1339 }
1341 }
1340
1342
1341 #[test]
1343 #[test]
tests/snapshots.rs+3−3
@@ -1714,7 +1714,7 @@ fn css_text_decoration_full() {
1714
1714
1715#[test]
1715#[test]
1716fn css_place_items() {
1716fn css_place_items() {
1717− let output = compile("@page T\n@el [grid, place-items center]");
1717+ let output = compile("@page T\n@el [display grid, place-items center]");
1718 assert!(output.contains("place-items:center"));
1718 assert!(output.contains("place-items:center"));
1719}
1719}
1720
1720
@@ -2015,7 +2015,7 @@ fn css_isolation() {
2015
2015
2016#[test]
2016#[test]
2017fn css_place_content() {
2017fn css_place_content() {
2018− let output = compile("@page T\n@el [grid, place-content center]");
2018+ let output = compile("@page T\n@el [display grid, place-content center]");
2019 assert!(output.contains("place-content:center"));
2019 assert!(output.contains("place-content:center"));
2020}
2020}
2021
2021
@@ -3375,7 +3375,7 @@ fn snapshot_layer_wrapping() {
3375#[test]
3375#[test]
3376fn grid_template_areas_passthrough() {
3376fn grid_template_areas_passthrough() {
3377 let output =
3377 let output =
3378− compile("@page T\n@el [grid, grid-template-areas \"a b\"]\n @el [grid-area a]\n A");
3378+ compile("@page T\n@el [display grid, grid-template-areas \"a b\"]\n @el [grid-area a]\n A");
3379 assert!(
3379 assert!(
3380 output.contains("grid-template-areas:\"a b\""),
3380 output.contains("grid-template-areas:\"a b\""),
3381 "grid-template-areas should pass through: {}",
3381 "grid-template-areas should pass through: {}",
tests/snapshots/extra_css.hl+1−1
@@ -9,7 +9,7 @@
9 Typography
9 Typography
10 @el [border-bottom 1 #ccc, border-top 2 red]
10 @el [border-bottom 1 #ccc, border-top 2 red]
11 Borders
11 Borders
12− @el [grid, grid-cols 3, gap 10]
12+ @el [display grid, grid-cols 3, gap 10]
13 @el [col-span 2]
13 @el [col-span 2]
14 Wide
14 Wide
15 @el
15 @el
tests/snapshots/grid_areas.hl+1−1
@@ -1,5 +1,5 @@
1@page Grid Areas
1@page Grid Areas
2−@el [grid, grid-template-areas "header header" "sidebar main" "footer footer", gap 20]
2+@el [display grid, grid-template-areas "header header" "sidebar main" "footer footer", gap 20]
3 @el [grid-area header, padding 10, background #3b82f6, color white]
3 @el [grid-area header, padding 10, background #3b82f6, color white]
4 Header
4 Header
5 @el [grid-area sidebar, padding 10, background #e5e7eb]
5 @el [grid-area sidebar, padding 10, background #e5e7eb]
tests/snapshots/new_css_properties.hl+1−1
@@ -20,7 +20,7 @@
20
20
21@text [text-decoration underline, text-decoration-color red, text-decoration-thickness 2, text-decoration-style wavy] Decorated
21@text [text-decoration underline, text-decoration-color red, text-decoration-thickness 2, text-decoration-style wavy] Decorated
22
22
23−@el [grid, place-items center]
23+@el [display grid, place-items center]
24 @text Centered
24 @text Centered
25
25
26@el [place-self center]
26@el [place-self center]
tests/snapshots/new_css_properties_2.hl+1−1
@@ -29,7 +29,7 @@
29@el [isolation isolate]
29@el [isolation isolate]
30 @text Isolated stacking
30 @text Isolated stacking
31
31
32−@el [grid, place-content center]
32+@el [display grid, place-content center]
33 @text Centered grid content
33 @text Centered grid content
34
34
35@el [background-image url(bg.png), background-size cover]
35@el [background-image url(bg.png), background-size cover]