mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-30 01:43:36 +08:00
Previously, the property was incorrectly validated on grid or flex containers. The correct place is to validate it on grid or flex items. Drive-by: improved the readability of the test because expectedResult was ambigious. Fixed: 1402019 Change-Id: I30c66f17b88b1618d9da8e66645fccf657b5992c Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/4116525 Reviewed-by: Ergün Erdoğmuş <ergunsh@chromium.org> Commit-Queue: Ergün Erdoğmuş <ergunsh@chromium.org>
659 lines
23 KiB
TypeScript
659 lines
23 KiB
TypeScript
// Copyright 2022 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 * as Host from '../../core/host/host.js';
|
|
import * as i18n from '../../core/i18n/i18n.js';
|
|
|
|
import {
|
|
buildPropertyDefinitionText,
|
|
buildPropertyName,
|
|
buildPropertyValue,
|
|
isFlexContainer,
|
|
isGridContainer,
|
|
isInlineElement,
|
|
isMulticolContainer,
|
|
isPossiblyReplacedElement,
|
|
} from './CSSRuleValidatorHelper.js';
|
|
|
|
const UIStrings = {
|
|
/**
|
|
*@description The message shown in the Style pane when the user hovers over a property that has no effect due to some other property.
|
|
*@example {flex-wrap: nowrap} REASON_PROPERTY_DECLARATION_CODE
|
|
*@example {align-content} AFFECTED_PROPERTY_DECLARATION_CODE
|
|
*/
|
|
ruleViolatedBySameElementRuleReason:
|
|
'The {REASON_PROPERTY_DECLARATION_CODE} property prevents {AFFECTED_PROPERTY_DECLARATION_CODE} from having an effect.',
|
|
/**
|
|
*@description The message shown in the Style pane when the user hovers over a property declaration that has no effect due to some other property.
|
|
*@example {flex-wrap} PROPERTY_NAME
|
|
@example {nowrap} PROPERTY_VALUE
|
|
*/
|
|
ruleViolatedBySameElementRuleFix: 'Try setting {PROPERTY_NAME} to something other than {PROPERTY_VALUE}.',
|
|
/**
|
|
*@description The message shown in the Style pane when the user hovers over a property declaration that has no effect due to the current property value.
|
|
*@example {display: block} EXISTING_PROPERTY_DECLARATION
|
|
*@example {display: flex} TARGET_PROPERTY_DECLARATION
|
|
*/
|
|
ruleViolatedBySameElementRuleChangeSuggestion:
|
|
'Try setting the {EXISTING_PROPERTY_DECLARATION} property to {TARGET_PROPERTY_DECLARATION}.',
|
|
/**
|
|
*@description The message shown in the Style pane when the user hovers over a property declaration that has no effect due to properties of the parent element.
|
|
*@example {display: block} REASON_PROPERTY_DECLARATION_CODE
|
|
*@example {flex} AFFECTED_PROPERTY_DECLARATION_CODE
|
|
*/
|
|
ruleViolatedByParentElementRuleReason:
|
|
'The {REASON_PROPERTY_DECLARATION_CODE} property on the parent element prevents {AFFECTED_PROPERTY_DECLARATION_CODE} from having an effect.',
|
|
/**
|
|
*@description The message shown in the Style pane when the user hovers over a property declaration that has no effect due to the properties of the parent element.
|
|
*@example {display: block} EXISTING_PARENT_ELEMENT_RULE
|
|
*@example {display: flex} TARGET_PARENT_ELEMENT_RULE
|
|
*/
|
|
ruleViolatedByParentElementRuleFix:
|
|
'Try setting the {EXISTING_PARENT_ELEMENT_RULE} property on the parent to {TARGET_PARENT_ELEMENT_RULE}.',
|
|
};
|
|
const str_ = i18n.i18n.registerUIStrings('panels/elements/CSSRuleValidator.ts', UIStrings);
|
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
|
|
export const enum HintType {
|
|
INACTIVE_PROPERTY = 'ruleValidation',
|
|
DEPRECATED_PROPERTY = 'deprecatedProperty',
|
|
}
|
|
|
|
export class Hint {
|
|
readonly #hintMessage: string;
|
|
readonly #possibleFixMessage: string|null;
|
|
readonly #learnMoreLink: string|undefined;
|
|
|
|
constructor(hintMessage: string, possibleFixMessage: string|null, learnMoreLink?: string) {
|
|
this.#hintMessage = hintMessage;
|
|
this.#possibleFixMessage = possibleFixMessage;
|
|
this.#learnMoreLink = learnMoreLink;
|
|
}
|
|
|
|
getMessage(): string {
|
|
return this.#hintMessage;
|
|
}
|
|
|
|
getPossibleFixMessage(): string|null {
|
|
return this.#possibleFixMessage;
|
|
}
|
|
|
|
getLearnMoreLink(): string|undefined {
|
|
return this.#learnMoreLink;
|
|
}
|
|
}
|
|
|
|
export abstract class CSSRuleValidator {
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.Other;
|
|
}
|
|
|
|
readonly #affectedProperties: string[];
|
|
|
|
constructor(affectedProperties: string[]) {
|
|
this.#affectedProperties = affectedProperties;
|
|
}
|
|
|
|
getApplicableProperties(): string[] {
|
|
return this.#affectedProperties;
|
|
}
|
|
|
|
abstract getHint(
|
|
propertyName: string, computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>,
|
|
nodeName?: string): Hint|undefined;
|
|
}
|
|
|
|
export class AlignContentValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super(['align-content']);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.AlignContent;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>): boolean {
|
|
if (computedStyles === null || computedStyles === undefined) {
|
|
return true;
|
|
}
|
|
if (!isFlexContainer(computedStyles)) {
|
|
return true;
|
|
}
|
|
return computedStyles.get('flex-wrap') !== 'nowrap';
|
|
}
|
|
|
|
getHint(_propertyName: string, computedStyles?: Map<string, string>): Hint|undefined {
|
|
if (this.#isRuleValid(computedStyles)) {
|
|
return;
|
|
}
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('flex-wrap', 'nowrap');
|
|
const affectedPropertyDeclarationCode = buildPropertyName('align-content');
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleFix, {
|
|
PROPERTY_NAME: buildPropertyName('flex-wrap'),
|
|
PROPERTY_VALUE: buildPropertyValue('nowrap'),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class FlexItemValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super(['flex', 'flex-basis', 'flex-grow', 'flex-shrink']);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.FlexItem;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): boolean {
|
|
if (parentComputedStyles === null) {
|
|
return true;
|
|
}
|
|
return isFlexContainer(parentComputedStyles);
|
|
}
|
|
|
|
getHint(propertyName: string, computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): Hint
|
|
|undefined {
|
|
if (this.#isRuleValid(computedStyles, parentComputedStyles)) {
|
|
return;
|
|
}
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('display', parentComputedStyles?.get('display'));
|
|
const affectedPropertyDeclarationCode = buildPropertyName(propertyName);
|
|
const targetParentPropertyDeclaration = buildPropertyDefinitionText('display', 'flex');
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedByParentElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedByParentElementRuleFix, {
|
|
'EXISTING_PARENT_ELEMENT_RULE': reasonPropertyDeclaration,
|
|
'TARGET_PARENT_ELEMENT_RULE': targetParentPropertyDeclaration,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class FlexContainerValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super(['flex-direction', 'flex-flow', 'flex-wrap']);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.FlexContainer;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>): boolean {
|
|
if (computedStyles === null) {
|
|
return true;
|
|
}
|
|
return isFlexContainer(computedStyles);
|
|
}
|
|
|
|
getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined {
|
|
if (this.#isRuleValid(computedStyles)) {
|
|
return;
|
|
}
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display'));
|
|
const targetRuleCode = buildPropertyDefinitionText('display', 'flex');
|
|
const affectedPropertyDeclarationCode = buildPropertyName(propertyName);
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleChangeSuggestion, {
|
|
'EXISTING_PROPERTY_DECLARATION': reasonPropertyDeclaration,
|
|
'TARGET_PROPERTY_DECLARATION': targetRuleCode,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class GridContainerValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super([
|
|
'grid',
|
|
'grid-auto-columns',
|
|
'grid-auto-flow',
|
|
'grid-auto-rows',
|
|
'grid-template',
|
|
'grid-template-areas',
|
|
'grid-template-columns',
|
|
'grid-template-rows',
|
|
]);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.GridContainer;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>): boolean {
|
|
return isGridContainer(computedStyles);
|
|
}
|
|
|
|
getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined {
|
|
if (this.#isRuleValid(computedStyles)) {
|
|
return;
|
|
}
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display'));
|
|
const targetRuleCode = buildPropertyDefinitionText('display', 'grid');
|
|
const affectedPropertyDeclarationCode = buildPropertyName(propertyName);
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleChangeSuggestion, {
|
|
'EXISTING_PROPERTY_DECLARATION': reasonPropertyDeclaration,
|
|
'TARGET_PROPERTY_DECLARATION': targetRuleCode,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class GridItemValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super([
|
|
'grid-area',
|
|
'grid-column',
|
|
'grid-row',
|
|
'grid-row-end',
|
|
'grid-row-start',
|
|
// At the time of writing (November 2022), `justify-self` is only in effect in grid layout.
|
|
// There are no other browsers that support `justify-self` in other layouts.
|
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Alignment/Box_Alignment_In_Block_Abspos_Tables
|
|
// TODO: move `justify-self` to other validator or change pop-over text if Chrome supports CSS Align in other layouts.
|
|
'justify-self',
|
|
]);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.GridItem;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): boolean {
|
|
if (!parentComputedStyles) {
|
|
return true;
|
|
}
|
|
return isGridContainer(parentComputedStyles);
|
|
}
|
|
|
|
getHint(propertyName: string, computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): Hint
|
|
|undefined {
|
|
if (this.#isRuleValid(computedStyles, parentComputedStyles)) {
|
|
return;
|
|
}
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('display', parentComputedStyles?.get('display'));
|
|
const targetParentPropertyDeclaration = buildPropertyDefinitionText('display', 'grid');
|
|
const affectedPropertyDeclarationCode = buildPropertyName(propertyName);
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedByParentElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedByParentElementRuleFix, {
|
|
'EXISTING_PARENT_ELEMENT_RULE': reasonPropertyDeclaration,
|
|
'TARGET_PARENT_ELEMENT_RULE': targetParentPropertyDeclaration,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class FlexOrGridItemValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super([
|
|
'place-self',
|
|
'align-self',
|
|
'order',
|
|
]);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.FlexOrGridItem;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): boolean {
|
|
if (!parentComputedStyles) {
|
|
return true;
|
|
}
|
|
return isFlexContainer(parentComputedStyles) || isGridContainer(parentComputedStyles);
|
|
}
|
|
|
|
getHint(propertyName: string, computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): Hint
|
|
|undefined {
|
|
if (this.#isRuleValid(computedStyles, parentComputedStyles)) {
|
|
return;
|
|
}
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('display', parentComputedStyles?.get('display'));
|
|
const targetParentPropertyDeclaration =
|
|
`${buildPropertyDefinitionText('display', 'flex')} or ${buildPropertyDefinitionText('display', 'grid')}`;
|
|
const affectedPropertyDeclarationCode = buildPropertyName(propertyName);
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedByParentElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedByParentElementRuleFix, {
|
|
'EXISTING_PARENT_ELEMENT_RULE': reasonPropertyDeclaration,
|
|
'TARGET_PARENT_ELEMENT_RULE': targetParentPropertyDeclaration,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class FlexGridValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super([
|
|
'justify-content',
|
|
'align-content',
|
|
'place-content', // Shorthand <'align-content'> <'justify-content'>?
|
|
'align-items',
|
|
]);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.FlexGrid;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>): boolean {
|
|
if (computedStyles === null) {
|
|
return true;
|
|
}
|
|
return isFlexContainer(computedStyles) || isGridContainer(computedStyles);
|
|
}
|
|
|
|
getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined {
|
|
if (this.#isRuleValid(computedStyles)) {
|
|
return;
|
|
}
|
|
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display'));
|
|
const affectedPropertyDeclarationCode = buildPropertyName(propertyName);
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleFix, {
|
|
PROPERTY_NAME: buildPropertyName('display'),
|
|
PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('display') as string),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class MulticolFlexGridValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super([
|
|
'gap',
|
|
'column-gap',
|
|
'row-gap',
|
|
'grid-gap',
|
|
'grid-column-gap',
|
|
'grid-column-end',
|
|
'grid-row-gap',
|
|
]);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.MulticolFlexGrid;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>): boolean {
|
|
if (computedStyles === null) {
|
|
return true;
|
|
}
|
|
return isMulticolContainer(computedStyles) || isFlexContainer(computedStyles) || isGridContainer(computedStyles);
|
|
}
|
|
|
|
getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined {
|
|
if (this.#isRuleValid(computedStyles)) {
|
|
return;
|
|
}
|
|
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display'));
|
|
const affectedPropertyDeclarationCode = buildPropertyName(propertyName);
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleFix, {
|
|
PROPERTY_NAME: buildPropertyName('display'),
|
|
PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('display') as string),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class PaddingValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super([
|
|
'padding',
|
|
'padding-top',
|
|
'padding-right',
|
|
'padding-bottom',
|
|
'padding-left',
|
|
]);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.Padding;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>): boolean {
|
|
const display = computedStyles?.get('display');
|
|
if (!display) {
|
|
return true;
|
|
}
|
|
return !['table-row-group', 'table-header-group', 'table-footer-group', 'table-row', 'table-column-group',
|
|
'table-column']
|
|
.includes(display);
|
|
}
|
|
|
|
getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined {
|
|
if (this.#isRuleValid(computedStyles)) {
|
|
return;
|
|
}
|
|
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display'));
|
|
const affectedPropertyDeclarationCode = buildPropertyName(propertyName);
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleFix, {
|
|
PROPERTY_NAME: buildPropertyName('display'),
|
|
PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('display') as string),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class PositionValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super([
|
|
'top',
|
|
'right',
|
|
'bottom',
|
|
'left',
|
|
]);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.Position;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>): boolean {
|
|
const position = computedStyles?.get('position');
|
|
if (position === null || position === undefined) {
|
|
return true;
|
|
}
|
|
return position !== 'static';
|
|
}
|
|
|
|
getHint(propertyName: string, computedStyles?: Map<string, string>): Hint|undefined {
|
|
if (this.#isRuleValid(computedStyles)) {
|
|
return;
|
|
}
|
|
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('position', computedStyles?.get('position'));
|
|
const affectedPropertyDeclarationCode = buildPropertyName(propertyName);
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleFix, {
|
|
PROPERTY_NAME: buildPropertyName('position'),
|
|
PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('position') as string),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class ZIndexValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super([
|
|
'z-index',
|
|
]);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.ZIndex;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): boolean {
|
|
const position = computedStyles?.get('position');
|
|
if (!position) {
|
|
return true;
|
|
}
|
|
return ['absolute', 'relative', 'fixed', 'sticky'].includes(position) || isFlexContainer(parentComputedStyles);
|
|
}
|
|
|
|
getHint(propertyName: string, computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>): Hint
|
|
|undefined {
|
|
if (this.#isRuleValid(computedStyles, parentComputedStyles)) {
|
|
return;
|
|
}
|
|
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('position', computedStyles?.get('position'));
|
|
const affectedPropertyDeclarationCode = buildPropertyName(propertyName);
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleFix, {
|
|
PROPERTY_NAME: buildPropertyName('position'),
|
|
PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('position') as string),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates if CSS width/height are having an effect on an element.
|
|
* See "Applies to" in https://www.w3.org/TR/css-sizing-3/#propdef-width.
|
|
* See "Applies to" in https://www.w3.org/TR/css-sizing-3/#propdef-height.
|
|
*/
|
|
export class SizingValidator extends CSSRuleValidator {
|
|
constructor() {
|
|
super([
|
|
'width',
|
|
'height',
|
|
]);
|
|
}
|
|
|
|
getMetricType(): Host.UserMetrics.CSSHintType {
|
|
return Host.UserMetrics.CSSHintType.Sizing;
|
|
}
|
|
|
|
#isRuleValid(computedStyles?: Map<string, string>, nodeName?: string): boolean {
|
|
if (!computedStyles || !nodeName) {
|
|
return true;
|
|
}
|
|
if (!isInlineElement(computedStyles)) {
|
|
return true;
|
|
}
|
|
// See https://html.spec.whatwg.org/multipage/rendering.html#replaced-elements.
|
|
return isPossiblyReplacedElement(nodeName);
|
|
}
|
|
|
|
getHint(
|
|
propertyName: string, computedStyles?: Map<string, string>, parentComputedStyles?: Map<string, string>,
|
|
nodeName?: string): Hint|undefined {
|
|
if (this.#isRuleValid(computedStyles, nodeName)) {
|
|
return;
|
|
}
|
|
|
|
const reasonPropertyDeclaration = buildPropertyDefinitionText('display', computedStyles?.get('display'));
|
|
const affectedPropertyDeclarationCode = buildPropertyName(propertyName);
|
|
|
|
return new Hint(
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleReason, {
|
|
'REASON_PROPERTY_DECLARATION_CODE': reasonPropertyDeclaration,
|
|
'AFFECTED_PROPERTY_DECLARATION_CODE': affectedPropertyDeclarationCode,
|
|
}),
|
|
i18nString(UIStrings.ruleViolatedBySameElementRuleFix, {
|
|
PROPERTY_NAME: buildPropertyName('display'),
|
|
PROPERTY_VALUE: buildPropertyValue(computedStyles?.get('display') as string),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
const CSS_RULE_VALIDATORS = [
|
|
AlignContentValidator,
|
|
FlexContainerValidator,
|
|
FlexGridValidator,
|
|
FlexItemValidator,
|
|
GridContainerValidator,
|
|
GridItemValidator,
|
|
FlexOrGridItemValidator,
|
|
SizingValidator,
|
|
MulticolFlexGridValidator,
|
|
PaddingValidator,
|
|
PositionValidator,
|
|
ZIndexValidator,
|
|
];
|
|
|
|
const setupCSSRulesValidators = (): Map<string, CSSRuleValidator[]> => {
|
|
const validatorsMap = new Map<string, CSSRuleValidator[]>();
|
|
for (const validatorClass of CSS_RULE_VALIDATORS) {
|
|
const validator = new validatorClass();
|
|
const affectedProperties = validator.getApplicableProperties();
|
|
|
|
for (const affectedProperty of affectedProperties) {
|
|
let propertyValidators = validatorsMap.get(affectedProperty);
|
|
if (propertyValidators === undefined) {
|
|
propertyValidators = [];
|
|
}
|
|
propertyValidators.push(validator);
|
|
|
|
validatorsMap.set(affectedProperty, propertyValidators);
|
|
}
|
|
}
|
|
return validatorsMap;
|
|
};
|
|
|
|
export const cssRuleValidatorsMap: Map<string, CSSRuleValidator[]> = setupCSSRulesValidators();
|