From 11630b9f4c531b80fe6ad5f73c5da234ef860f8d Mon Sep 17 00:00:00 2001 From: raivaibhav Date: Fri, 8 Oct 2021 23:04:00 +0530 Subject: [PATCH] Add: cssFormat compiler option Fixes: https://github.com/sveltejs/svelte-repl/issues/178 --- src/compiler/compile/Component.ts | 3 +- src/compiler/compile/css/Selector.ts | 37 ++++- src/compiler/compile/css/Stylesheet.ts | 194 ++++++++++++++++++++----- src/compiler/compile/index.ts | 5 +- src/compiler/interfaces.ts | 1 + 5 files changed, 192 insertions(+), 48 deletions(-) diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 202e14fd98..9ba7c4594f 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -145,7 +145,8 @@ export default class Component { filename: compile_options.filename, component_name: name, dev: compile_options.dev, - get_css_hash: compile_options.cssHash + get_css_hash: compile_options.cssHash, + cssFormat: compile_options.cssFormat }); this.stylesheet.validate(this); diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index d9868f4530..a09f77c37a 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -1,5 +1,6 @@ import MagicString from 'magic-string'; -import Stylesheet from './Stylesheet'; +import { walk } from 'estree-walker'; +import Stylesheet, { overwrite } from './Stylesheet'; import { gather_possible_values, UNKNOWN } from './gather_possible_values'; import { CssNode } from './interfaces'; import Component from '../Component'; @@ -67,18 +68,44 @@ export default class Selector { } } - minify(code: MagicString) { + minify(code: MagicString, format: boolean = false) { let c: number = null; this.blocks.forEach((block, i) => { if (i > 0) { - if (block.start - c > 1) { - code.overwrite(c, block.start, block.combinator.name || ' '); + const name = block.combinator.name || ''; + const separator = format && name ? ` ${name} ` : name; + if (format) { + // this will add the ' ' around the combinator + overwrite(code, c, block.start, separator || ' '); + } else if (block.start - c > 1) { + code.overwrite( c, block.start, name); + } } c = block.end; }); - } + walk(this.node as any, { + enter: (node: any) => { + if (node && node.type === 'AttributeSelector') { + const char_at_start = code.original[node.start]; + const char_at_end = code.original[node.end - 1]; + const matcher = node.matcher || ''; + const flags = node.flags ? ` ${node.flags}` : ''; + const indentifier = node.name && node.name.type === 'Identifier' && node.name; + if (indentifier) { + const { name } = indentifier; + const value_start = node.value && node.value.start; + const content = `${char_at_start}${name}${format && value_start ? ' ' : ''}${matcher}${!value_start ? `${flags}${char_at_end}` : ''}`; + code.overwrite(node.start, value_start || node.end , content); + if (value_start) { + code.overwrite(value_start, node.end, `${node.value.value || ''}${flags}${char_at_end}`); + } + } + } + } + }); +} transform(code: MagicString, attr: string, max_amount_class_specificity_increased: number) { const amount_class_specificity_to_increase = max_amount_class_specificity_increased - this.blocks.filter(block => block.should_encapsulate).length; diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts index 39b7be9db6..d7fccc5442 100644 --- a/src/compiler/compile/css/Stylesheet.ts +++ b/src/compiler/compile/css/Stylesheet.ts @@ -21,19 +21,38 @@ const at_rule_has_declaration = ({ block }: CssNode): true => block.children && block.children.find((node: CssNode) => node.type === 'Declaration'); +export function overwrite ( + code: MagicString, + start: number, + end: number, + content: string +) { + if (end - start > 0 ) { + code.overwrite(start, end, content); + } else { + code.appendLeft(start, content); + } +} + function minify_declarations( code: MagicString, start: number, - declarations: Declaration[] + declarations: Declaration[], + format:boolean = false, + prepend_format: string = '' ): number { let c = start; declarations.forEach((declaration, i) => { - const separator = i > 0 ? ';' : ''; - if ((declaration.node.start - c) > separator.length) { + const default_separator = i > 0 ? ';' : ''; + // it will add \n and prepend string before every declaration + const separator = format ? `${default_separator}\n${prepend_format}` : default_separator; + if (format) { + overwrite(code, c, declaration.node.start, separator); + } else if ((declaration.node.start - c) > default_separator.length) { code.overwrite(c, declaration.node.start, separator); } - declaration.minify(code); + declaration.minify(code, format); c = declaration.node.end; }); @@ -50,7 +69,10 @@ class Rule { this.node = node; this.parent = parent; this.selectors = node.prelude.children.map((node: CssNode) => new Selector(node, stylesheet)); - this.declarations = node.block.children.map((node: CssNode) => new Declaration(node)); + this.declarations = node.block.children + .filter(node => node.type === 'Declaration') + .map((node: CssNode) => new Declaration(node)); + this.childrens = []; } apply(node: Element) { @@ -63,30 +85,42 @@ class Rule { return this.selectors.some(s => s.used); } - minify(code: MagicString, _dev: boolean) { + minify(code: MagicString, _dev: boolean, format: boolean = false, prepend_format: string = '') { let c = this.node.start; let started = false; - + // if (prepend_format && format) { + // code.appendLeft(c, prepend_format); + // } this.selectors.forEach((selector) => { if (selector.used) { - const separator = started ? ',' : ''; - if ((selector.node.start - c) > separator.length) { - code.overwrite(c, selector.node.start, separator); + const intial_separator = started ? ',' : ''; + //this will add \n after every selector in same rule + const separator = intial_separator && format ? `${intial_separator}\n` : intial_separator; + if ((selector.node.start - c) > intial_separator.length) { + overwrite(code, c, selector.node.start, separator); } - - selector.minify(code); + selector.minify(code, format); c = selector.node.end; started = true; } }); - code.remove(c, this.node.block.start); - + if (format) { + // this is to add a space before the rule start or say before { + overwrite(code, c, this.node.block.start, ' '); + } else { + code.remove(c, this.node.block.start); + } c = this.node.block.start + 1; - c = minify_declarations(code, c, this.declarations); + c = minify_declarations(code, c, this.declarations, format, prepend_format); + if (format) { + //add the next line just before rule end or say before } + overwrite(code, c, this.node.block.end - 1, `\n${prepend_format.replace('\t\t', '')}`); + } else { + code.remove(c, this.node.block.end - 1); + } - code.remove(c, this.node.block.end - 1); } transform(code: MagicString, id: string, keyframes: Map, max_amount_class_specificity_increased: number) { @@ -129,14 +163,14 @@ class Declaration { if (block.type === 'Identifier') { const name = block.name; if (keyframes.has(name)) { - code.overwrite(block.start, block.end, keyframes.get(name)); + overwrite(code, block.start, block.end, keyframes.get(name)); } } }); } } - minify(code: MagicString) { + minify(code: MagicString, format: boolean) { if (!this.node.property) return; // @apply, and possibly other weird cases? const c = this.node.start + this.node.property.length; @@ -147,7 +181,10 @@ class Declaration { let start = first.start; while (/\s/.test(code.original[start])) start += 1; - if (start - c > 1) { + if (format) { + // this will add the space before the decalaration value; + overwrite(code, c, start, ': '); + } else if (start - c > 1) { code.overwrite(c, start, ':'); } } @@ -182,56 +219,125 @@ class Atrule { return true; // TODO } - minify(code: MagicString, dev: boolean) { - if (this.node.name === 'media') { + minify(code: MagicString, dev: boolean, format: boolean = false, prepend_format: string = '') { + if (this.node.name === 'media') { const expression_char = code.original[this.node.prelude.start]; let c = this.node.start + (expression_char === '(' ? 6 : 7); - if (this.node.prelude.start > c) code.remove(c, this.node.prelude.start); + code.overwrite(this.node.start, this.node.prelude.start, '@media '); this.node.prelude.children.forEach((query: CssNode) => { // TODO minify queries c = query.end; }); - code.remove(c, this.node.block.start); + if (format) { + overwrite(code, c, this.node.block.start, ' '); + } else { + code.remove(c, this.node.block.start); + } + } else if (this.node.name === 'supports') { let c = this.node.start + 9; - if (this.node.prelude.start - c > 1) code.overwrite(c, this.node.prelude.start, ' '); + if (format) { + overwrite(code, c, this.node.prelude.start, ' '); + } else if (this.node.prelude.start - c > 1) { + code.overwrite(c, this.node.prelude.start, ' '); + } this.node.prelude.children.forEach((query: CssNode) => { // TODO minify queries c = query.end; }); - code.remove(c, this.node.block.start); + + if (format) { + overwrite(code, c, this.node.block.start, ' '); + } else { + code.remove(c, this.node.block.start); + } } else { let c = this.node.start + this.node.name.length + 1; if (this.node.prelude) { - if (this.node.prelude.start - c > 1) code.overwrite(c, this.node.prelude.start, ' '); + if (format) { + overwrite(code, c, this.node.prelude.start, ' '); + } else if (this.node.prelude.start - c > 1) { + code.overwrite(c, this.node.prelude.start, ' '); + } c = this.node.prelude.end; } - if (this.node.block && this.node.block.start - c > 0) { - code.remove(c, this.node.block.start); + if (this.node.block) { + if (format) { + overwrite(code, c, this.node.block.start, ' '); + } else if (this.node.block.start - c > 0) { + code.remove(c, this.node.block.start); + } } } // TODO other atrules - if (this.node.block) { + let c = this.node.block.start + 1; if (this.declarations.length) { - c = minify_declarations(code, c, this.declarations); + c = minify_declarations(code, c, this.declarations, format, prepend_format); // if the atrule has children, leave the last declaration semicolon alone if (this.children.length) c++; } this.children.forEach(child => { if (child.is_used(dev)) { - code.remove(c, child.node.start); - child.minify(code, dev); + if (format && prepend_format) { + + // this will add \n opening of atrule ie.e, after { + overwrite(code, c, child.node.start, '\n'); + + // this will add the necessary prepend space for case of nested child + code.appendLeft(child.node.start, prepend_format); + } else { + code.remove(c, child.node.start); + } + child.minify(code, dev, format, `${prepend_format}${prepend_format}`); c = child.node.end; } }); - - code.remove(c, this.node.block.end - 1); + if (format && prepend_format) { + // add \n and prepend string before the closing of the atrule or say before the } + // replace is a shortcut to ignore the first iteration + overwrite(code, c, this.node.block.end - 1, `\n${prepend_format.replace('\t\t', '')}`); + } else { + code.remove(c, this.node.block.end - 1); + } + } + if (this.node.prelude) { + walk(this.node.prelude.children as any, { + enter: (node: any, parent: any) => { + if (parent && parent.type === 'MediaFeature') { + const char_at_start = code.original[parent.start]; + const char_at_end = code.original[parent.end - 1]; + if (node) { + code.overwrite(parent.start, node.start , `${char_at_start}${parent.name}:${format ? ' ' : ''}`); + let value = ''; + switch (node.type) { + case 'Number' : + value = node.value; + break; + case 'Identifier': + value = node.name; + break; + case 'Dimension': + value = `${node.value}${node.unit}`; + break; + case 'Ratio': + value = `${node.start}/${node.left}`; + break; + default: + value = ''; + } + code.overwrite(node.start, parent.end, `${value}${char_at_end}`); + } else { + code.overwrite(parent.start, parent.end , `${char_at_start}${parent.name}${char_at_end}`); + } + } + } + }); } } @@ -294,6 +400,7 @@ export default class Stylesheet { keyframes: Map = new Map(); nodes_with_css_class: Set = new Set(); + cssFormat: boolean = false; constructor({ source, @@ -301,7 +408,8 @@ export default class Stylesheet { component_name, filename, dev, - get_css_hash = get_default_css_hash + get_css_hash = get_default_css_hash, + cssFormat }: { source: string; ast: Ast; @@ -309,11 +417,13 @@ export default class Stylesheet { component_name: string | undefined; dev: boolean; get_css_hash: CssHashGetter; + cssFormat?: boolean, }) { this.source = source; this.ast = ast; this.filename = filename; this.dev = dev; + this.cssFormat = cssFormat; if (ast.css && ast.css.children.length) { this.id = get_css_hash({ @@ -330,11 +440,10 @@ export default class Stylesheet { let current_atrule: Atrule = null; walk(ast.css as any, { - enter: (node: any) => { + enter: (node: any, parent: any) => { if (node.type === 'Atrule') { const atrule = new Atrule(node); stack.push(atrule); - if (current_atrule) { current_atrule.children.push(atrule); } else if (depth <= 1) { @@ -421,10 +530,15 @@ export default class Stylesheet { } let c = 0; - this.children.forEach(child => { + this.children.forEach((child, i) => { if (child.is_used(this.dev)) { - code.remove(c, child.node.start); - child.minify(code, this.dev); + // it will add \n\n after end of every rule + if (this.cssFormat && i > 0) { + overwrite(code, c, child.node.start, '\n\n'); + } else { + code.remove(c, child.node.start); + } + child.minify(code, this.dev, this.cssFormat, '\t\t'); c = child.node.end; } }); diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 96b24bceee..29f5413a06 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -31,7 +31,8 @@ const valid_options = [ 'loopGuardTimeout', 'preserveComments', 'preserveWhitespace', - 'cssHash' + 'cssHash', + 'cssFormat' ]; function validate_options(options: CompileOptions, warnings: Warning[]) { @@ -82,7 +83,7 @@ function validate_options(options: CompileOptions, warnings: Warning[]) { } export default function compile(source: string, options: CompileOptions = {}) { - options = Object.assign({ generate: 'dom', dev: false }, options); + options = Object.assign({ generate: 'dom', dev: false, cssFormat: false }, options); const stats = new Stats(); const warnings = []; diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index 7446c4c14b..6a7fab9348 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -165,6 +165,7 @@ export interface CompileOptions { preserveComments?: boolean; preserveWhitespace?: boolean; + cssFormat?: boolean; } export interface ParserOptions {