| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import type * as Common from '../../core/common/common.js'; |
| import * as Formatter from '../../models/formatter/formatter.js'; |
| import type * as Diff from '../../third_party/diff/diff.js'; |
| import * as DiffView from '../../ui/components/diff_view/diff_view.js'; |
| |
| export function imageNameForResourceType(resourceType: Common.ResourceType.ResourceType): string { |
| if (resourceType.isDocument()) { |
| return 'ic_file_document'; |
| } |
| if (resourceType.isImage()) { |
| return 'ic_file_image'; |
| } |
| if (resourceType.isFont()) { |
| return 'ic_file_font'; |
| } |
| if (resourceType.isScript()) { |
| return 'ic_file_script'; |
| } |
| if (resourceType.isStyleSheet()) { |
| return 'ic_file_stylesheet'; |
| } |
| if (resourceType.isWebbundle()) { |
| return 'ic_file_webbundle'; |
| } |
| return 'ic_file_default'; |
| } |
| |
| export async function formatCSSChangesFromDiff(diff: Diff.Diff.DiffArray): Promise<string> { |
| const indent = ' '; |
| const {originalLines, currentLines, rows} = DiffView.DiffView.buildDiffRows(diff); |
| const originalRuleMaps = await buildStyleRuleMaps(originalLines.join('\n')); |
| const currentRuleMaps = await buildStyleRuleMaps(currentLines.join('\n')); |
| |
| let changes = ''; |
| let recordedOriginalSelector, recordedCurrentSelector; |
| let hasOpenDeclarationBlock = false; |
| for (const {currentLineNumber, originalLineNumber, type} of rows) { |
| if (type !== DiffView.DiffView.RowType.Deletion && type !== DiffView.DiffView.RowType.Addition) { |
| continue; |
| } |
| |
| const isDeletion = type === DiffView.DiffView.RowType.Deletion; |
| const lines = isDeletion ? originalLines : currentLines; |
| // Diff line arrays starts at 0, but line numbers start at 1. |
| const lineIndex = isDeletion ? originalLineNumber - 1 : currentLineNumber - 1; |
| const line = lines[lineIndex].trim(); |
| const {declarationIDToStyleRule, styleRuleIDToStyleRule} = isDeletion ? originalRuleMaps : currentRuleMaps; |
| let styleRule; |
| let prefix = ''; |
| if (declarationIDToStyleRule.has(lineIndex)) { |
| styleRule = declarationIDToStyleRule.get(lineIndex) as FormattableStyleRule; |
| const selector = styleRule.selector; |
| // Use the equality of selector strings as a best-effort check for the equality of style rules. |
| if (selector !== recordedOriginalSelector && selector !== recordedCurrentSelector) { |
| prefix += `${selector} {\n`; |
| } |
| prefix += indent; |
| hasOpenDeclarationBlock = true; |
| } else { |
| if (hasOpenDeclarationBlock) { |
| prefix = '}\n\n'; |
| hasOpenDeclarationBlock = false; |
| } |
| if (styleRuleIDToStyleRule.has(lineIndex)) { |
| styleRule = styleRuleIDToStyleRule.get(lineIndex); |
| } |
| } |
| |
| const processedLine = isDeletion ? `/* ${line} */` : line; |
| changes += prefix + processedLine + '\n'; |
| if (isDeletion) { |
| recordedOriginalSelector = styleRule?.selector; |
| } else { |
| recordedCurrentSelector = styleRule?.selector; |
| } |
| } |
| |
| if (changes.length > 0) { |
| changes += '}'; |
| } |
| return changes; |
| } |
| |
| interface FormattableStyleRule { |
| rule: Formatter.FormatterWorkerPool.CSSRule; |
| selector: string; |
| } |
| |
| async function buildStyleRuleMaps(content: string): Promise<{ |
| declarationIDToStyleRule: Map<number, FormattableStyleRule>, |
| styleRuleIDToStyleRule: Map<number, FormattableStyleRule>, |
| }> { |
| const rules = await new Promise<Formatter.FormatterWorkerPool.CSSRule[]>(res => { |
| const rules: Formatter.FormatterWorkerPool.CSSRule[] = []; |
| Formatter.FormatterWorkerPool.formatterWorkerPool().parseCSS(content, (isLastChunk, currentRules) => { |
| rules.push(...currentRules); |
| if (isLastChunk) { |
| res(rules); |
| } |
| }); |
| }); |
| |
| // We use line numbers as unique IDs for rules and declarations |
| const declarationIDToStyleRule = new Map<number, FormattableStyleRule>(); |
| const styleRuleIDToStyleRule = new Map<number, FormattableStyleRule>(); |
| for (const rule of rules) { |
| if ('styleRange' in rule) { |
| const selector = rule.selectorText.split('\n').pop()?.trim(); |
| if (!selector) { |
| continue; |
| } |
| const styleRule = {rule, selector}; |
| styleRuleIDToStyleRule.set(rule.styleRange.startLine, styleRule); |
| for (const property of rule.properties) { |
| declarationIDToStyleRule.set(property.range.startLine, styleRule); |
| } |
| } |
| } |
| return {declarationIDToStyleRule, styleRuleIDToStyleRule}; |
| } |