Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Color format checking for bslint #94

Merged
merged 37 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9b77bc6
Color format checking - first step/ example
charlie-abbott-deltatre Jul 27, 2023
504ae4a
Color format checking
charlie-abbott-deltatre Jul 27, 2023
4566bdd
Color format checking
charlie-abbott-deltatre Jul 27, 2023
b8ac58f
Color format, alpha, default alpha, case and color cert requirement c…
charlie-abbott-deltatre Jul 28, 2023
fcd7117
Adding alpha to luma check and removing todo
charlie-abbott-deltatre Jul 28, 2023
0fd23b3
Updating readme
charlie-abbott-deltatre Jul 28, 2023
994bb34
Moving color validate functions to util for use in multiple files
charlie-abbott-deltatre Jul 30, 2023
56247f9
Updating readme
charlie-abbott-deltatre Jul 30, 2023
a4e3941
Removing isXMLFile checking
charlie-abbott-deltatre Jul 30, 2023
d1036eb
Temp example for traversing to XML node arrtibute values
charlie-abbott-deltatre Jul 31, 2023
c2b5dd3
Moving XML color validation to codeStyle
charlie-abbott-deltatre Aug 2, 2023
a35513c
Moving XML color validation to codeStyle
charlie-abbott-deltatre Aug 2, 2023
5f9769b
Moving XML color validation to codeStyle
charlie-abbott-deltatre Aug 2, 2023
77563e5
Starting refactor
charlie-abbott-deltatre Aug 3, 2023
bac6d4c
Continuing refactor
charlie-abbott-deltatre Aug 4, 2023
abdcfc3
Continuing refactor
charlie-abbott-deltatre Aug 23, 2023
7e4c8fe
Continuing refactor
charlie-abbott-deltatre Sep 15, 2023
e615f8c
Merge branch 'master' into color-style-checking
TwitchBronBron Sep 18, 2023
a70a3bd
Removing XML file checks - will add these in a seperate PR
charlie-abbott-deltatre Sep 18, 2023
8b1a3f9
Continuing refactor
charlie-abbott-deltatre Sep 19, 2023
ec353ae
PR comment fixes
charlie-abbott-deltatre Sep 19, 2023
3c4426d
Continuing refactor
charlie-abbott-deltatre Sep 19, 2023
01d09ae
Continuing refactor
charlie-abbott-deltatre Sep 20, 2023
8143d12
Continuing refactor
charlie-abbott-deltatre Sep 20, 2023
ec2393e
Fixing build errors
charlie-abbott-deltatre Sep 23, 2023
0071fa8
Including non color string checks
charlie-abbott-deltatre Sep 23, 2023
6ba9eb8
Removing initial code for fixes. Due to add in a future PR
charlie-abbott-deltatre Sep 24, 2023
6699af0
Including non color string checks
charlie-abbott-deltatre Sep 24, 2023
fa402c4
Continuing refactor
charlie-abbott-deltatre Sep 24, 2023
64727f9
Move all test brs/bs code inline
TwitchBronBron Sep 25, 2023
283a1c6
Merge branch 'master' of https://github.com/rokucommunity/bslint into…
TwitchBronBron Sep 25, 2023
e22c357
Continuing refactor
charlie-abbott-deltatre Sep 25, 2023
a6b708d
Fix templatestring quasi handling
TwitchBronBron Sep 25, 2023
776ed2f
Merge branch 'color-style-checking' of https://github.com/disc7/bslin…
TwitchBronBron Sep 25, 2023
77a9ea0
Remove unused color test files
TwitchBronBron Sep 25, 2023
7b30db8
Removing breakpoint
charlie-abbott-deltatre Sep 25, 2023
2f20601
only validate string-like template strings
TwitchBronBron Sep 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ Default rules:

"type-annotations": "off",

"color-format": "off",
"color-case": "off",
"color-alpha": "off",
"color-alpha-defaults": "off",
"color-cert": "off",

"assign-all-paths": "error",
"unsafe-path-loop": "error",
"unsafe-iterators": "error",
Expand Down Expand Up @@ -210,6 +216,38 @@ Default rules:
- `never` enforces that files do not end with a newline
- `off`: do not validate

- `color-format`: ensures that all the color values follow the same prefix formatting. Can also use to prevent any colors values from being defined in the code-base (brs or bs files), except for values in a stand-alone file (ie. theme file).

- `hash-hex`: enforces all color values are type string or template string and use a `#` prefix
- `quoted-numeric-hex`: enforces all color values are type string or template string and use a `0x` prefix
- `never`: enforces that no color values can be defined in the code-base (brs or bs files). Useful if you define colors in a separate stand-alone file. To use this option you would list your stand-alone file in the `ignore` list or `diagnosticFilters`.
- `off`: do not validate (**default**)

- `color-case`: ensures that all color values follow the same case. Requires that `color-format` is set to `hash-hex` or `quoted-numeric-hex`.

- `lower`: enforces all color values that are type string or template string and all lowercase. ie. `#11bbdd`
- `upper`: enforces all color values that are type string or template string and all uppercase. ie. `#EEAA44`
- `off`: do not validate (**default**)

- `color-alpha`: defines the usage of the color alpha value. ie. `#xxxxxxFF`. Requires that `color-format` is set to `hash-hex` or `quoted-numeric-hex`.

- `always`: enforces all color values that are type string or template string define an alpha value
- `allowed`: allows color values that are type string or template string to define an alpha value
- `never`: enforces that none of the color values that are type string or template string define an alpha value
- `off`: do not validate (**default**)

- `color-alpha-defaults`: enforces default color-alpha values. ie. `#xxxxxxFF` or `#xxxxxx00`. Requires that `color-alpha` is not set to `off` and `color-format` is set to `hash-hex` or `quoted-numeric-hex`.

- `allowed`: allows both types of defaults to be used
- `only-hidden`: only allows opacity 0% (hidden) from being used
- `never`: enforces that no defaults can be used
- `off`: do not validate (**default**)

- `color-cert`: enforces Roku's [broadcast safe color 6.4 certification requirement](https://developer.roku.com/en-gb/docs/developer-program/certification/certification.md). Requires that `color-format` is set to `hash-hex` or `quoted-numeric-hex`.

- `always`: ensures all white and black color-format values either match or are darker/ lighter than the minimum recommended values. For white the minimum value is `#DBDBDB` and for black the minimum value is `#161616`
- `off`: do not validate (**default**)

### Strictness rules

- `type-annotations`: validation of presence of `as` type annotations, for function
Expand Down
105 changes: 105 additions & 0 deletions src/createColorValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { BsDiagnostic, Range } from 'brighterscript';
import { messages } from './plugins/codeStyle/diagnosticMessages';
import { BsLintRules, RuleColorFormat, RuleColorCase, RuleColorAlpha, RuleColorAlphaDefaults, RuleColorCertCompliant } from './index';

export function createColorValidator(severity: Readonly<BsLintRules>) {
const { colorFormat, colorCase, colorAlpha, colorAlphaDefaults, colorCertCompliant } = severity;
return (text, range, diagnostics) => {
const len = text.length;
if (len < 7 || len > 12) {
// we're only interested in string length is between 7 (#DBDBDB) to 12 ("0xDBDBDBff") chars long
return;
}

const hashHexRegex = /#[0-9A-Fa-f]{6}/g;
TwitchBronBron marked this conversation as resolved.
Show resolved Hide resolved
const quotedNumericHexRegex = /0x[0-9A-Fa-f]{6}/g;
const hashHexMatches = (text.startsWith('#') || text.startsWith('"#')) ? text.match(hashHexRegex) : undefined;
const quotedNumericHexMatches = (text.startsWith('0x') || text.startsWith('"0x')) ? text.match(quotedNumericHexRegex) : undefined;

if ((colorFormat === 'never') && (quotedNumericHexMatches || hashHexMatches)) {
diagnostics.push(messages.expectedColorFormat(range));
return;
}
const hashHexAlphaRegex = /#[0-9A-Fa-f]{8}/g;
const quotedNumericHexAlphaRegex = /0x[0-9A-Fa-f]{8}/g;

if (colorFormat === 'hash-hex') {
if (quotedNumericHexMatches) {
diagnostics.push(messages.expectedColorFormat(range));
}
validateColorCase(hashHexMatches, range, diagnostics, colorCase, colorFormat);
disc7 marked this conversation as resolved.
Show resolved Hide resolved
validateColorAlpha(text.match(hashHexAlphaRegex), hashHexMatches, quotedNumericHexMatches, range, diagnostics, colorAlpha, colorAlphaDefaults);
validateColorCertCompliance(hashHexMatches, range, diagnostics, colorFormat, colorCertCompliant);

} else if (colorFormat === 'quoted-numeric-hex') {
if (hashHexMatches) {
diagnostics.push(messages.expectedColorFormat(range));
}
validateColorCase(quotedNumericHexMatches, range, diagnostics, colorCase, colorFormat);
validateColorAlpha(text.match(quotedNumericHexAlphaRegex), hashHexMatches, quotedNumericHexMatches, range, diagnostics, colorAlpha, colorAlphaDefaults);
validateColorCertCompliance(quotedNumericHexMatches, range, diagnostics, colorFormat, colorCertCompliant);
}
};
}

function validateColorAlpha(alphaMatches: RegExpMatchArray, hashMatches: RegExpMatchArray, quotedNumericHexMatches: RegExpMatchArray, range: Range, diagnostics: (Omit<BsDiagnostic, 'file'>)[], alpha: RuleColorAlpha, alphaDefaults: RuleColorAlphaDefaults) {
const validateColorAlpha = (alpha === 'never' || alpha === 'always' || alpha === 'allowed');
if (validateColorAlpha) {
if (alpha === 'never' && alphaMatches) {
diagnostics.push(messages.expectedColorAlpha(range));
}
if ((alpha === 'always' && alphaMatches === null) && (hashMatches || quotedNumericHexMatches)) {
diagnostics.push(messages.expectedColorAlpha(range));
}
if ((alphaDefaults === 'never' || alphaDefaults === 'only-hidden') && alphaMatches) {
for (let i = 0; i < alphaMatches.length; i++) {
const colorHashAlpha = alphaMatches[i];
const alphaValue = colorHashAlpha.slice(-2).toLowerCase();
if (alphaValue === 'ff' || (alphaDefaults === 'never' && alphaValue === '00')) {
diagnostics.push(messages.expectedColorAlphaDefaults(range));
}
}
}
}
}

function validateColorCase(matches: RegExpMatchArray, range: Range, diagnostics: (Omit<BsDiagnostic, 'file'>)[], colorCase: RuleColorCase, colorFormat: RuleColorFormat) {
const validateColorCase = colorCase === 'upper' || colorCase === 'lower';
if (validateColorCase && matches) {
let colorValue = matches[0];
const charsToStrip = (colorFormat === 'hash-hex') ? 1 : 2;
colorValue = colorValue.substring(charsToStrip);
if (colorCase === 'lower' && colorValue !== colorValue.toLowerCase()) {
diagnostics.push(messages.expectedColorCase(range));
}
if (colorCase === 'upper' && colorValue !== colorValue.toUpperCase()) {
diagnostics.push(messages.expectedColorCase(range));
}
}
}

function validateColorCertCompliance(matches: RegExpMatchArray, range: Range, diagnostics: (Omit<BsDiagnostic, 'file'>)[], colorFormat: RuleColorFormat, certCompliant: RuleColorCertCompliant) {
const validateCertCompliant = certCompliant === 'always';
if (validateCertCompliant && matches) {
const BROADCAST_SAFE_BLACK = '161616';
const BROADCAST_SAFE_WHITE = 'DBDBDB';
const MAX_BLACK_LUMA = getColorLuma(BROADCAST_SAFE_BLACK);
const MAX_WHITE_LUMA = getColorLuma(BROADCAST_SAFE_WHITE);
let colorValue = matches[0];
const charsToStrip = (colorFormat === 'hash-hex') ? 1 : 2;
colorValue = colorValue.substring(charsToStrip);
const colorLuma = getColorLuma(colorValue);
if (colorLuma > MAX_WHITE_LUMA || colorLuma < MAX_BLACK_LUMA) {
diagnostics.push(messages.colorCertCompliance(range));
}
}
}

function getColorLuma(value: string) {
const rgb = parseInt(value, 16); // Convert rrggbb to decimal
const red = (rgb >> 16) & 0xff; // eslint-disable-line no-bitwise
const green = (rgb >> 8) & 0xff; // eslint-disable-line no-bitwise
const blue = (rgb >> 0) & 0xff; // eslint-disable-line no-bitwise
// Per ITU-R BT.709
return 0.2126 * red + 0.7152 * green + 0.0722 * blue; // eslint-disable-line
}
15 changes: 15 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export type RuleFunction = 'no-function' | 'no-sub' | 'auto' | 'off';
export type RuleAAComma = 'always' | 'no-dangling' | 'never' | 'off';
export type RuleTypeAnnotations = 'all' | 'return' | 'args' | 'off';
export type RuleEolLast = 'always' | 'never' | 'off';
export type RuleColorFormat = 'hash-hex' | 'quoted-numeric-hex' | 'never' | 'off';
export type RuleColorCase = 'upper' | 'lower' | 'off';
export type RuleColorAlpha = 'always' | 'allowed' | 'never' | 'off';
export type RuleColorAlphaDefaults = 'allowed' | 'only-hidden' | 'never' | 'off';
export type RuleColorCertCompliant = 'always' | 'off'; // Roku cert requirement for broadcast safe colors. 6.4

export type BsLintConfig = Pick<BsConfig, 'project' | 'rootDir' | 'files' | 'cwd' | 'watch'> & {
lintConfig?: string;
Expand All @@ -39,6 +44,11 @@ export type BsLintConfig = Pick<BsConfig, 'project' | 'rootDir' | 'files' | 'cwd
// Will be transformed to RegExp type when program context is created.
'todo-pattern'?: string;
'eol-last'?: RuleEolLast;
'color-format'?: RuleColorFormat;
TwitchBronBron marked this conversation as resolved.
Show resolved Hide resolved
'color-case'?: RuleColorCase;
'color-alpha'?: RuleColorAlpha;
'color-alpha-defaults'?: RuleColorAlphaDefaults;
'color-cert'?: RuleColorCertCompliant;
};
globals?: string[];
ignores?: string[];
Expand Down Expand Up @@ -67,6 +77,11 @@ export interface BsLintRules {
noTodo: BsLintSeverity;
noStop: BsLintSeverity;
eolLast: RuleEolLast;
colorFormat: RuleColorFormat;
colorCase: RuleColorCase;
colorAlpha: RuleColorAlpha;
colorAlphaDefaults: RuleColorAlphaDefaults;
colorCertCompliant: RuleColorCertCompliant;
}

export { Linter };
Expand Down
42 changes: 41 additions & 1 deletion src/plugins/codeStyle/diagnosticMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ export enum CodeStyleError {
NoTodo = 'LINT3015',
NoStop = 'LINT3016',
EolLastMissing = 'LINT3017',
EolLastFound = 'LINT3018'
EolLastFound = 'LINT3018',
ColorFormat = 'LINT3019',
ColorCase = 'LINT3020',
ColorAlpha = 'LINT3021',
ColorAlphaDefaults = 'LINT3022',
ColorCertCompliant = 'LINT3023'
}

const CS = 'Code style:';
Expand Down Expand Up @@ -159,5 +164,40 @@ export const messages = {
source: 'bslint',
message: `${CS} File should not end with a newline`,
range
}),
expectedColorFormat: (range: Range) => ({
severity: DiagnosticSeverity.Error,
code: CodeStyleError.ColorFormat,
source: 'bslint',
message: `${CS} File should follow color format`,
range
}),
expectedColorCase: (range: Range) => ({
severity: DiagnosticSeverity.Error,
code: CodeStyleError.ColorCase,
source: 'bslint',
message: `${CS} File should follow color case`,
range
}),
expectedColorAlpha: (range: Range) => ({
severity: DiagnosticSeverity.Error,
code: CodeStyleError.ColorAlpha,
source: 'bslint',
message: `${CS} File should follow color alpha rule`,
range
}),
expectedColorAlphaDefaults: (range: Range) => ({
severity: DiagnosticSeverity.Error,
code: CodeStyleError.ColorAlphaDefaults,
source: 'bslint',
message: `${CS} File should follow color alpha defaults rule`,
range
}),
colorCertCompliance: (range: Range) => ({
severity: DiagnosticSeverity.Error,
code: CodeStyleError.ColorCertCompliant,
source: 'bslint',
message: `${CS} File should follow Roku broadcast safe color cert requirement`,
range
})
};