Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/beautify/beautify-html.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ export interface IBeautifyHTMLOptions {
* default ""
*/
unformatted_content_delimiter?: string;

/**
* Options for the CSS sub-formatter used when formatting embedded CSS in style tags.
* Properties in this object are promoted to top-level CSS options via js-beautify's _mergeOpts.
*/
css?: {
selector_separator_newline?: boolean;
newline_between_rules?: boolean;
space_around_selector_separator?: boolean;
brace_style?: 'collapse' | 'expand';
preserve_newlines?: boolean;
max_preserve_newlines?: number;
};
}

export interface IBeautifyHTML {
Expand Down
14 changes: 14 additions & 0 deletions src/htmlLanguageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ export interface HTMLFormatConfiguration {
templating?: ('auto' | 'none' | 'angular' | 'django' | 'erb' | 'handlebars' | 'php' | 'smarty')[] | boolean;
unformattedContentDelimiter?: string;

/**
* Options for formatting embedded CSS in style tags.
* These are forwarded to the CSS sub-formatter used by js-beautify.
*/
css?: EmbeddedCSSFormatConfiguration;
}

export interface EmbeddedCSSFormatConfiguration {
newlineBetweenSelectors?: boolean;
newlineBetweenRules?: boolean;
spaceAroundSelectorSeparator?: boolean;
braceStyle?: 'collapse' | 'expand';
preserveNewLines?: boolean;
maxPreserveNewLines?: number;
}

export interface HoverSettings {
Expand Down
16 changes: 16 additions & 0 deletions src/services/htmlFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export function format(document: TextDocument, range: Range | undefined, options
indent_scripts: getFormatOption(options, 'indentScripts', 'normal'),
templating: getTemplatingFormatOption(options, 'all'),
unformatted_content_delimiter: getFormatOption(options, 'unformattedContentDelimiter', ''),
css: getCSSFormatOption(options),
};

let result = html_beautify(trimLeft(value), htmlOptions);
Expand Down Expand Up @@ -133,6 +134,21 @@ function getTemplatingFormatOption(options: HTMLFormatConfiguration, dflt: strin
return value;
}

function getCSSFormatOption(options: HTMLFormatConfiguration): IBeautifyHTMLOptions['css'] {
const css = options.css;
if (!css) {
return undefined;
}
return {
selector_separator_newline: css.newlineBetweenSelectors,
newline_between_rules: css.newlineBetweenRules,
space_around_selector_separator: css.spaceAroundSelectorSeparator,
brace_style: css.braceStyle,
preserve_newlines: css.preserveNewLines,
max_preserve_newlines: css.maxPreserveNewLines,
};
}

function computeIndentLevel(content: string, offset: number, options: HTMLFormatConfiguration): number {
let i = offset;
let nChars = 0;
Expand Down
229 changes: 228 additions & 1 deletion src/test/formatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { suite, test } from 'node:test';
import { getLanguageService, TextDocument, Range } from '../htmlLanguageService.js';
import { getLanguageService, TextDocument, Range, HTMLFormatConfiguration } from '../htmlLanguageService.js';
import * as assert from 'node:assert';

suite('HTML Formatter', () => {
Expand Down Expand Up @@ -347,3 +347,230 @@ suite('HTML Formatter', () => {
});

});

suite('HTML Formatter - Embedded CSS', () => {

function formatWithOptions(unformatted: string, expected: string, options: HTMLFormatConfiguration) {
const uri = 'test://test.html';
const document = TextDocument.create(uri, 'html', 0, unformatted);
const edits = getLanguageService().format(document, undefined, options);
const formatted = TextDocument.applyEdits(document, edits);
assert.equal(formatted, expected);
}

test('css.newlineBetweenSelectors: false', () => {
var content = [
'<html>',
'',
'<head>',
' <style>',
' h1, h2 { color: red; }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

var expected = [
'<html>',
'',
'<head>',
' <style>',
' h1, h2 {',
' color: red;',
' }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

formatWithOptions(content, expected, {
tabSize: 2,
insertSpaces: true,
css: { newlineBetweenSelectors: false }
});
});

test('css.newlineBetweenSelectors: true (default)', () => {
var content = [
'<html>',
'',
'<head>',
' <style>',
' h1, h2 { color: red; }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

var expected = [
'<html>',
'',
'<head>',
' <style>',
' h1,',
' h2 {',
' color: red;',
' }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

formatWithOptions(content, expected, {
tabSize: 2,
insertSpaces: true,
css: { newlineBetweenSelectors: true }
});
});

test('css.newlineBetweenRules: false', () => {
var content = [
'<html>',
'',
'<head>',
' <style>',
' h1 { color: red; }',
' h2 { color: blue; }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

var expected = [
'<html>',
'',
'<head>',
' <style>',
' h1 {',
' color: red;',
' }',
' h2 {',
' color: blue;',
' }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

formatWithOptions(content, expected, {
tabSize: 2,
insertSpaces: true,
css: { newlineBetweenRules: false }
});
});

test('css.newlineBetweenRules: true (default)', () => {
var content = [
'<html>',
'',
'<head>',
' <style>',
' h1 { color: red; }',
' h2 { color: blue; }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

var expected = [
'<html>',
'',
'<head>',
' <style>',
' h1 {',
' color: red;',
' }',
'',
' h2 {',
' color: blue;',
' }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

formatWithOptions(content, expected, {
tabSize: 2,
insertSpaces: true,
css: { newlineBetweenRules: true }
});
});

test('css.spaceAroundSelectorSeparator: true', () => {
var content = [
'<html>',
'',
'<head>',
' <style>',
' div>span { color: red; }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

var expected = [
'<html>',
'',
'<head>',
' <style>',
' div > span {',
' color: red;',
' }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

formatWithOptions(content, expected, {
tabSize: 2,
insertSpaces: true,
css: { spaceAroundSelectorSeparator: true }
});
});

test('no css options passed - uses defaults', () => {
var content = [
'<html>',
'',
'<head>',
' <style>',
' h1, h2 { color: red; }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

// Default: newlineBetweenSelectors is true
var expected = [
'<html>',
'',
'<head>',
' <style>',
' h1,',
' h2 {',
' color: red;',
' }',
' </style>',
'</head>',
'',
'</html>',
].join('\n');

formatWithOptions(content, expected, {
tabSize: 2,
insertSpaces: true,
});
});

});
Loading