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 19 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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, except for values in a stand-alone file (ie. theme file).

- `hashHex`: enforces all color values are type string and use a `#` prefix
- `quotedNumericHex`: enforces all color values are type string and use a `0x` prefix
- `never`: enforces that no color values can be defined in the code-base. 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 `hashHex` or `quotedNumericHex`.

- `lower`: enforces all color values that are type string are all lowercase. ie. `#11bbdd`
- `upper`: enforces all color values that are type string are 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 `hashHex` or `quotedNumericHex`.

- `always`: enforces all color values that are type string define an alpha value
- `allowed`: allows color values that are type string to define an alpha value
- `never`: enforces that none of the color values that are type 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 `hashHex` or `quotedNumericHex`.

- `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 `hashHex` or `quotedNumericHex`.

- `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
115 changes: 115 additions & 0 deletions src/createColorValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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) => {
// remove quotes if any exist
text = text.replace(/['"]+/g, '');
disc7 marked this conversation as resolved.
Show resolved Hide resolved
const len = text.length;
if (len < 7 || len > 10) {
// we're only interested in string length is between 7 (#DBDBDB) to 10 (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.match(hashHexRegex);
disc7 marked this conversation as resolved.
Show resolved Hide resolved
const quotedNumericHexMatches = text.match(quotedNumericHexRegex);

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regexes can be defined outside createColorValidator function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you post an example?

const quotedNumericHexAlphaRegex = /0x[0-9A-Fa-f]{8}/g;

if (text.startsWith('#') && colorFormat === 'hashHex') {
if (quotedNumericHexMatches) {
diagnostics.push(messages.expectedColorFormat(range));
}
validateColorCase(hashHexMatches, range, diagnostics, colorCase, colorFormat);
validateColorAlpha(text.match(hashHexAlphaRegex), hashHexMatches, quotedNumericHexMatches, range, diagnostics, colorAlpha, colorAlphaDefaults);
validateColorCertCompliance(hashHexMatches, range, diagnostics, colorFormat, colorCertCompliant);

} else if (text.startsWith('0x') && colorFormat === 'quotedNumericHex') {
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));
// debugger;
disc7 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
}

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 === 'hashHex') ? 1 : 2;
colorValue = colorValue.substring(charsToStrip);
for (let i = 0; i < colorValue.length; i++) {
const char = colorValue.charAt(i);
if (colorCase === 'lower' && char === char.toUpperCase() && char !== char.toLowerCase()) {
diagnostics.push(messages.expectedColorCase(range));
break;
}
if (colorCase === 'upper' && char === char.toLowerCase() && char !== char.toUpperCase()) {
diagnostics.push(messages.expectedColorCase(range));
break;
}
}
}
}

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 === 'hashHex') ? 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 = 'hashHex' | 'quotedNumericHex' | 'never' | 'off';
disc7 marked this conversation as resolved.
Show resolved Hide resolved
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
})
};
Loading