UNPKG

@rokucommunity/bslint

Version:
995 lines (993 loc) 51.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const chai_1 = require("chai"); const brighterscript_1 = require("brighterscript"); const Linter_1 = require("../../Linter"); const index_1 = require("./index"); const index_2 = require("../../index"); const util_1 = require("../../util"); const testHelpers_spec_1 = require("../../testHelpers.spec"); const diagnosticMessages_1 = require("./diagnosticMessages"); describe('codeStyle', () => { let linter; let lintContext; let program; const project1 = { rootDir: 'test/project1' }; /** * By default, all rules are off. So turn on the ones you care about for this test */ function init(rules) { program = new brighterscript_1.Program({ rules: Object.assign({ 'assign-all-paths': 'off', 'unsafe-path-loop': 'off', 'unsafe-iterators': 'off', 'unreachable-code': 'off', 'case-sensitivity': 'off', 'unused-variable': 'off', 'consistent-return': 'off', 'no-stop': 'off', 'inline-if-style': 'off', 'block-if-style': 'off', 'condition-style': 'off', 'named-function-style': 'off', 'anon-function-style': 'off', 'aa-comma-style': 'off', 'type-annotations': 'off', 'no-print': 'off', 'no-todo': 'off', 'todo-pattern': 'off', 'eol-last': 'off', 'color-format': 'off', 'color-case': 'off', 'color-alpha': 'off', 'color-alpha-defaults': 'off', 'color-cert': 'off', 'no-regex-duplicates': 'off' }, (rules !== null && rules !== void 0 ? rules : {})) }); program.plugins.add((0, index_2.default)()); program.plugins.emit('afterProgramCreate', program); return program; } beforeEach(() => { linter = new Linter_1.default(); init(); linter.builder.plugins.add({ name: 'test', afterProgramCreate: (program) => { lintContext = (0, util_1.createContext)(program); const codeStyle = new index_1.default(lintContext); program.plugins.add(codeStyle); } }); }); describe('validate inline if', () => { it('allows anything when set to off', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/inline-if.brs'], rules: { 'inline-if-style': 'off', 'block-if-style': 'off', 'condition-style': 'off', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); }); it('disallow inline if when set to never', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/inline-if.brs'], rules: { 'inline-if-style': 'never', 'block-if-style': 'off', 'condition-style': 'off', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `02:LINT3001:Code style: no inline if statement allowed`, `06:LINT3001:Code style: no inline if statement allowed` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('require `then` for inline if', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/inline-if.brs'], rules: { 'inline-if-style': 'then', 'block-if-style': 'off', 'condition-style': 'off', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `06:LINT3002:Code style: add 'then' keyword` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('disallow `then` for inline if', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/inline-if.brs'], rules: { 'inline-if-style': 'no-then', 'block-if-style': 'off', 'condition-style': 'off', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `02:LINT3003:Code style: remove 'then' keyword` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); }); describe('validate block if', () => { it('allows anything when set to off', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/block-if.brs'], rules: { 'inline-if-style': 'off', 'block-if-style': 'off', 'condition-style': 'off', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); }); it('require `then` for block if', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/block-if.brs'], rules: { 'inline-if-style': 'off', 'block-if-style': 'then', 'condition-style': 'off', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `10:LINT3004:Code style: add 'then' keyword`, `12:LINT3004:Code style: add 'then' keyword` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('disallow `then` for block if', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/block-if.brs'], rules: { 'inline-if-style': 'off', 'block-if-style': 'no-then', 'condition-style': 'off', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `02:LINT3005:Code style: remove 'then' keyword`, `04:LINT3005:Code style: remove 'then' keyword` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); }); describe('validate condition style', () => { it('allows anything when set to off', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/conditions.brs'], rules: { 'inline-if-style': 'off', 'block-if-style': 'off', 'condition-style': 'off', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); }); it('requires parenthesis around conditions', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/conditions.brs'], rules: { 'inline-if-style': 'off', 'block-if-style': 'off', 'condition-style': 'group', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `03:LINT3006:Code style: add parenthesis around condition`, `05:LINT3006:Code style: add parenthesis around condition` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('disallow parenthesis around conditions', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/conditions.brs'], rules: { 'inline-if-style': 'off', 'block-if-style': 'off', 'condition-style': 'no-group', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `12:LINT3007:Code style: remove parenthesis around condition`, `14:LINT3007:Code style: remove parenthesis around condition` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); }); describe('validate function style', () => { it('enforce no-function style', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/function-style.brs'], rules: { 'named-function-style': 'no-function', 'anon-function-style': 'no-function', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `08:LINT3008:Code style: expected 'sub' keyword (always use 'sub')`, `10:LINT3008:Code style: expected 'sub' keyword (always use 'sub')`, `16:LINT3008:Code style: expected 'sub' keyword (always use 'sub')` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('enforce no-sub style', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/no-sub-style.brs'], rules: { 'named-function-style': 'no-sub', 'anon-function-style': 'no-sub', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `08:LINT3009:Code style: expected 'function' keyword (always use 'function')`, `10:LINT3009:Code style: expected 'function' keyword (always use 'function')` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('enforce auto style', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/auto-function-style.brs'], rules: { 'named-function-style': 'auto', 'anon-function-style': 'auto', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `22:LINT3009:Code style: expected 'function' keyword (use 'function' when a value is returned)`, `23:LINT3009:Code style: expected 'function' keyword (use 'function' when a value is returned)`, `24:1141:Void sub may not return a value`, `26:1141:Void sub may not return a value`, `29:LINT3008:Code style: expected 'sub' keyword (use 'sub' when no value is returned)`, `31:LINT3008:Code style: expected 'sub' keyword (use 'sub' when no value is returned)`, `36:LINT3008:Code style: expected 'sub' keyword (use 'sub' when no value is returned)`, `38:LINT3008:Code style: expected 'sub' keyword (use 'sub' when no value is returned)` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); }); describe('type annotations', () => { it('do nothing when disabled', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/type-annotations.brs'], rules: { 'named-function-style': 'off', 'anon-function-style': 'off', 'type-annotations': 'off', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); }); }); it('enforce return type only', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/type-annotations.brs'], rules: { 'named-function-style': 'off', 'anon-function-style': 'off', 'type-annotations': 'return', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `05:LINT3010:Strictness: function should declare the return type` ]; (0, chai_1.expect)(actual).deep.equal(expected); // should only highlight the function name (0, chai_1.expect)(diagnostics[0].range).to.eql(brighterscript_1.util.createRange(4, 0, 4, 8)); }); it('enforce arguments type only', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/type-annotations.brs'], rules: { 'named-function-style': 'off', 'anon-function-style': 'off', 'type-annotations': 'args', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `01:LINT3011:Strictness: type annotation required`, `05:LINT3011:Strictness: type annotation required` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('enforce all annotations', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/type-annotations.brs'], rules: { 'named-function-style': 'off', 'anon-function-style': 'off', 'type-annotations': 'all', 'no-print': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `01:LINT3011:Strictness: type annotation required`, `05:LINT3010:Strictness: function should declare the return type`, `05:LINT3011:Strictness: type annotation required` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('enforce no print', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/no-print.brs'], rules: { 'no-print': 'error' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `02:LINT3012:Code style: Avoid using direct Print statements`, `03:LINT3012:Code style: Avoid using direct Print statements` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); describe('enforce no todo', () => { it('default todo pattern', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/no-todo.brs'], rules: { 'no-todo': 'error' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `02:LINT3015:Code style: Avoid using TODO comments`, `04:LINT3015:Code style: Avoid using TODO comments`, `06:LINT3015:Code style: Avoid using TODO comments`, `08:LINT3015:Code style: Avoid using TODO comments`, '10:LINT3015:Code style: Avoid using TODO comments', '12:LINT3015:Code style: Avoid using TODO comments' ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('modified todo pattern', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/no-todo.brs'], rules: { 'no-todo': 'error', 'todo-pattern': 'PLEASEFIX' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = ['19:LINT3015:Code style: Avoid using TODO comments']; (0, chai_1.expect)(actual).deep.equal(expected); }); }); it('enforce no stop', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/no-stop.brs'], // Should be warn by default rules: { 'no-stop': 'error' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `03:LINT3016:Code style: STOP statements are not allowed in published applications` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); describe('enforce eol at end of file', () => { it('eol always', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/no-eol-last.brs'], rules: { 'eol-last': 'always' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `03:LINT3017:Code style: File should end with a newline` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('eol never', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/eol-last.brs'], rules: { 'eol-last': 'never' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `03:LINT3018:Code style: File should not end with a newline` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('empty file', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/empty.brs'], rules: { 'eol-last': 'always' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); }); it('off without eol', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/no-eol-last.brs'], rules: { 'eol-last': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); }); it('off with eol', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/eol-last.brs'], rules: { 'eol-last': 'off' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); }); }); it('enforce no assocarray component field type', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['components/interfaceTest.xml'], rules: { 'no-assocarray-component-field-type': 'info' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `06:LINT3024:Avoid using field type 'assocarray'`, `07:LINT3024:Avoid using field type 'assocarray'` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('enforce no array component field type', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['components/interfaceTest.xml'], rules: { 'no-array-component-field-type': 'info' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `04:LINT3025:Avoid using field type 'array'`, `08:LINT3025:Avoid using field type 'array'` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); describe('AA style', () => { it('collects wrapping AA members indexes', () => { const { statements } = brighterscript_1.Parser.parse(` aa = {} aa = { p1: 1 } aa = { p1: 1, p2: 2 } aa = { 'comment p1: 1, 'comment p2: 2 'comment } aa = { p1: 1, 'comment p2: 2 'comment } aa = { p1: 1 } aa = { p1: 1, p2: 2 } aa = { p1: 1, p2: 2, } `, { mode: brighterscript_1.ParseMode.BrightScript }).ast; const indexes = statements.map(s => { const assign = s; const value = assign.value; return (0, index_1.collectWrappingAAMembersIndexes)(value); }); (0, chai_1.expect)(indexes).to.deep.equal([ [], [0], [0, 1], [1, 3], [0, 2], [0], [1], [1] ]); }); it('enforce aa comma, never', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/aa-style.brs'], rules: { 'aa-comma-style': 'never' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `03:LINT3013:Remove optional comma`, `04:LINT3013:Remove optional comma`, `11:LINT3013:Remove optional comma`, `12:LINT3013:Remove optional comma`, `13:LINT3013:Remove optional comma`, `31:LINT3013:Remove optional comma` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('enforce aa comma, no dangling', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/aa-style.brs'], rules: { 'aa-comma-style': 'no-dangling' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `13:LINT3013:Remove optional comma`, `19:LINT3014:Add comma after the expression`, `20:LINT3014:Add comma after the expression`, `31:LINT3013:Remove optional comma` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); it('enforce aa comma, always', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/aa-style.brs'], rules: { 'aa-comma-style': 'always' } })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = [ `05:LINT3014:Add comma after the expression`, `19:LINT3014:Add comma after the expression`, `20:LINT3014:Add comma after the expression`, `21:LINT3014:Add comma after the expression`, `31:LINT3013:Remove optional comma` ]; (0, chai_1.expect)(actual).deep.equal(expected); }); }); describe('enforce no regex re-creation', () => { it('within loop', () => { init({ 'no-regex-duplicates': 'warn' }); program.setFile('source/main.brs', ` sub init() ? type(5) for i = 0 to 10 CreateObject("roRegex", "test", "") for i = 0 to 10 if false ? type(5) CreateObject("roRegex", "test2", "") end if CreateObject("roRegex", "test3", "") end for end for end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, [ '05:LINT3026:Avoid redeclaring identical regular expressions in a loop', '11:LINT3026:Avoid redeclaring identical regular expressions in a loop' ]); }); it('no error within if', () => { init({ 'no-regex-duplicates': 'warn' }); program.setFile('source/main.brs', ` sub init() CreateObject("roRegex", "test", "") ? type(5) if false CreateObject("roRegex", "test", "") end if end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, []); }); it('anonymous function', () => { init({ 'no-regex-duplicates': 'warn' }); program.setFile('source/main.brs', ` sub init() CreateObject("roRegex", "test", "") someAnonFunc = sub() CreateObject("roRegex", "test", "") ? type(5) CreateObject("roRegex", "test2", "") temp = CreateObject("roRegex", "test2", "") end sub end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, ['08:LINT3026:Avoid redeclaring identical regular expressions']); }); }); describe('color-format', () => { it('finds colors in various templateString expression styles', () => { /* eslint-disable no-template-curly-in-string */ doTest('print `0xffffff`', [7, 15]); // string-like doTest('print `${"0xffffff"}`', [9, 19]); // expression with a string in it doTest('print `0xffffff${"0xffffff"}`', [17, 27]); // color then expression doTest('print `${"0xffffff"}0xffffff`', [9, 19]); // expression then color doTest('print `${"0xffffff"}0xffffff${"0xffffff"}`', [9, 19], [30, 40]); // expression then color then expression doTest('print `0xffffff${"0xffffff"}0xffffff`', [17, 27]); // color then expression then color function doTest(code, ...diagnosticCharLocations) { init({ 'color-format': 'quoted-numeric-hex', 'color-case': 'upper' }); program.setFile('source/main.bs', `sub init()\n${code}\nend sub`); program.validate(); (0, testHelpers_spec_1.expectDiagnostics)(program, diagnosticCharLocations.map(x => diagnosticMessages_1.messages.expectedColorCase(brighterscript_1.util.createRange(1, x[0], 1, x[1])))); } /* eslint-enable no-template-curly-in-string */ }); it('quoted-numeric-hex & uppercase', () => { init({ 'color-format': 'quoted-numeric-hex', 'color-case': 'upper' }); program.setFile('source/main.brs', ` sub init() colors = ["0xff0000", "0x00FF00"] nonValidColorsWrongColorFormat = [ "#xxffff" ' this string is skipped because xx is not valid hex ] end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, [ '03:LINT3020:Code style: File should follow color case' ]); }); it('hash-hex & lowercase', () => { init({ 'color-format': 'hash-hex', 'color-case': 'lower' }); program.setFile('source/main.brs', ` sub init() colors = { value1: "#0000ff", value2: "#00FFff", value3: "#ff0000", shortFormColorHash: "#f0F" } end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, [ '05:LINT3020:Code style: File should follow color case' ]); }); it('has-hex & lowercase & template strings', () => { init({ 'color-format': 'hash-hex', 'color-case': 'lower' }); program.setFile('source/main.bs', ` sub init() colors = { value1: \`#00FFff\`, value2: \`#0000ff\`, value3: \`#ff0000\`, value4: \`#ff00FF\`, shortFormColorHash: \`#f0F\` } end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, [ '04:LINT3020:Code style: File should follow color case', '07:LINT3020:Code style: File should follow color case' ]); }); it('BRS file color format is none - no color values found', () => { init({ 'color-format': 'never' }); program.setFile('source/main.bs', ` sub init() colorLengthStringNoColorValues = "abcdefg" colorLengthStringInvalidColorValues = "0xxx0000" shortFormColorHash = "#f00" shortFormColorQuoteNumeric = "0xf00" colorLengthStringNoColorValues = \`abcdefg\` colorLengthStringInvalidColorValues = \`0xxx0000\` shortFormColorHash = \`#f00\` shortFormColorQuoteNumeric = \`0xf00\` end sub `); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, []); }); it('color-format:never but color values found', () => { init({ 'color-format': 'never' }); program.setFile('source/main.bs', ` sub init() colors = { value1: "#0000ff", value2: "#00FFff", value3: "#ff0000", shortFormColorHash: "#f0F" } end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, [ '04:LINT3019:Code style: File should follow color format', '05:LINT3019:Code style: File should follow color format', '06:LINT3019:Code style: File should follow color format' ]); }); it('quoted-numeric-hex & color-cert:always', () => { init({ 'color-format': 'quoted-numeric-hex', 'color-cert': 'always' }); program.setFile('source/main.bs', ` sub init() color = "0xEBEBEC" color = "0x161616" color = "0xEBEBEBFF" color = "0x161615" longStringWithColors = "Long string value with 0x161615 non broadcast safe color values defined" end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, [ '03:LINT3023:Code style: File should follow Roku broadcast safe color cert requirement', '06:LINT3023:Code style: File should follow Roku broadcast safe color cert requirement' ]); }); it('quoted-numeric-hex & color-cert:off', () => { init({ 'color-format': 'quoted-numeric-hex', 'color-cert': 'off' }); program.setFile('source/main.bs', ` sub init() color = "0xDBDBDC" color = "0x161616" color = "0xEBEBEBFF" color = "0x161615" longStringWithColors = "Long string value with 0x161615 non broadcast safe color values defined" end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, []); }); it('quoted-numeric-hex, color-alpha:allowed, color-alpha-defaults:never', () => { init({ 'color-format': 'quoted-numeric-hex', 'color-alpha': 'allowed', 'color-alpha-defaults': 'never' }); program.setFile('source/main.bs', ` sub init() color = "0xf00000" color = "0xff0000cc" color = "0xfff000" color = "0xffff0000" color = "0xfffff0FF" colorLengthStringNoColorValues = "abcdefg" colorLengthStringInvalidColorValues = "0xxx0000" shortFormColorHash = "#f00" shortFormColorQuoteNumeric = "0xf00" end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, [ '06:LINT3022:Code style: File should follow color alpha defaults rule', '07:LINT3022:Code style: File should follow color alpha defaults rule' ]); }); it('quoted-numeric-hex, alpha values are allowed and only hidden alpha (00) defaults are allowed', () => { init({ 'color-format': 'quoted-numeric-hex', 'color-alpha': 'allowed', 'color-alpha-defaults': 'only-hidden' }); program.setFile('source/main.bs', ` sub init() color = "0xf00000" color = "0xff0000cc" color = "0xfff000" color = "0xffff0000" color = "0xfffff0FF" colorLengthStringNoColorValues = "abcdefg" colorLengthStringInvalidColorValues = "0xxx0000" shortFormColorHash = "#f00" shortFormColorQuoteNumeric = "0xf00" end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, [ '07:LINT3022:Code style: File should follow color alpha defaults rule' ]); }); it('quoted-numeric-hex and alpha values are not allowed', () => { init({ 'color-format': 'quoted-numeric-hex', 'color-alpha': 'never' }); program.setFile('source/main.bs', ` sub init() color = "0xf00000" color = "0xff0000cc" color = "0xfff000" color = "0xffff0000" color = "0xfffff0FF" colorLengthStringNoColorValues = "abcdefg" colorLengthStringInvalidColorValues = "0xxx0000" shortFormColorHash = "#f00" shortFormColorQuoteNumeric = "0xf00" end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, [ '04:LINT3021:Code style: File should follow color alpha rule', '06:LINT3021:Code style: File should follow color alpha rule', '07:LINT3021:Code style: File should follow color alpha rule' ]); }); it('quoted-numeric-hex and alpha values are required', () => { init({ 'color-format': 'quoted-numeric-hex', 'color-alpha': 'always' }); program.setFile('source/main.bs', ` sub init() color = "0xf00000" color = "0xff0000cc" color = "0xfff000" color = "0xffff0000" color = "0xfffff0FF" colorLengthStringNoColorValues = "abcdefg" colorLengthStringInvalidColorValues = "0xxx0000" shortFormColorHash = "#f00" shortFormColorQuoteNumeric = "0xf00" end sub `); program.validate(); (0, testHelpers_spec_1.expectDiagnosticsFmt)(program, [ '03:LINT3021:Code style: File should follow color alpha rule', '05:LINT3021:Code style: File should follow color alpha rule' ]); }); }); describe('fix', () => { // Filenames (without the extension) that we want to copy with a "-temp" suffix const tmpFileNames = [ 'function-style', 'if-style', 'aa-style', 'eol-last', 'no-eol-last', 'single-line' ]; beforeEach(() => { tmpFileNames.forEach(filename => fs.copyFileSync(`${project1.rootDir}/source/${filename}.brs`, `${project1.rootDir}/source/${filename}-temp.brs`)); }); afterEach(() => { // Clear temp files tmpFileNames.forEach(filename => fs.unlinkSync(`${project1.rootDir}/source/${filename}-temp.brs`)); }); it('replaces `sub` with `function`', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/function-style-temp.brs'], rules: { 'named-function-style': 'no-function', 'anon-function-style': 'no-function', 'no-print': 'off' }, fix: true })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(1); await lintContext.applyFixes(); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(0); const actualSrc = fs.readFileSync(`${project1.rootDir}/source/function-style-temp.brs`).toString(); const expectedSrc = fs.readFileSync(`${project1.rootDir}/source/function-style-nofun.brs`).toString(); (0, chai_1.expect)(actualSrc).to.equal(expectedSrc); }); it('replaces `function` with `sub`', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/function-style-temp.brs'], rules: { 'named-function-style': 'no-sub', 'anon-function-style': 'no-sub', 'no-print': 'off' }, fix: true })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(1); await lintContext.applyFixes(); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(0); const actualSrc = fs.readFileSync(`${project1.rootDir}/source/function-style-temp.brs`).toString(); const expectedSrc = fs.readFileSync(`${project1.rootDir}/source/function-style-nosub.brs`).toString(); (0, chai_1.expect)(actualSrc).to.equal(expectedSrc); }); it('removes optional `then`', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/if-style-temp.brs'], rules: { 'named-function-style': 'off', 'block-if-style': 'no-then', 'inline-if-style': 'no-then', 'condition-style': 'off', 'no-print': 'off' }, fix: true })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(1); await lintContext.applyFixes(); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(0); const actualSrc = fs.readFileSync(`${project1.rootDir}/source/if-style-temp.brs`).toString(); const expectedSrc = fs.readFileSync(`${project1.rootDir}/source/if-style-nothen.brs`).toString(); (0, chai_1.expect)(actualSrc).to.equal(expectedSrc); }); it('adds optional `then`', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/if-style-temp.brs'], rules: { 'named-function-style': 'off', 'block-if-style': 'then', 'inline-if-style': 'then', 'condition-style': 'off', 'no-print': 'off' }, fix: true })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(1); await lintContext.applyFixes(); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(0); const actualSrc = fs.readFileSync(`${project1.rootDir}/source/if-style-temp.brs`).toString(); const expectedSrc = fs.readFileSync(`${project1.rootDir}/source/if-style-then.brs`).toString(); (0, chai_1.expect)(actualSrc).to.equal(expectedSrc); }); it('remove optional condition group', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/if-style-temp.brs'], rules: { 'named-function-style': 'off', 'block-if-style': 'off', 'inline-if-style': 'off', 'condition-style': 'no-group', 'no-print': 'off' }, fix: true })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(1); await lintContext.applyFixes(); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(0); const actualSrc = fs.readFileSync(`${project1.rootDir}/source/if-style-temp.brs`).toString(); const expectedSrc = fs.readFileSync(`${project1.rootDir}/source/if-style-nogroup.brs`).toString(); (0, chai_1.expect)(actualSrc).to.equal(expectedSrc); }); it('add optional condition group', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/if-style-temp.brs'], rules: { 'named-function-style': 'off', 'block-if-style': 'off', 'inline-if-style': 'off', 'condition-style': 'group', 'no-print': 'off' }, fix: true })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(1); await lintContext.applyFixes(); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(0); const actualSrc = fs.readFileSync(`${project1.rootDir}/source/if-style-temp.brs`).toString(); const expectedSrc = fs.readFileSync(`${project1.rootDir}/source/if-style-group.brs`).toString(); (0, chai_1.expect)(actualSrc).to.equal(expectedSrc); }); it('remove optional aa comma', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/aa-style-temp.brs'], rules: { 'named-function-style': 'off', 'anon-function-style': 'off', 'no-print': 'off', 'aa-comma-style': 'never' }, fix: true })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(1); await lintContext.applyFixes(); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(0); const actualSrc = fs.readFileSync(`${project1.rootDir}/source/aa-style-temp.brs`).toString(); const expectedSrc = fs.readFileSync(`${project1.rootDir}/source/aa-style-nocomma.brs`).toString(); (0, chai_1.expect)(actualSrc).to.equal(expectedSrc); }); it('add missing aa comma, always', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/aa-style-temp.brs'], rules: { 'named-function-style': 'off', 'anon-function-style': 'off', 'no-print': 'off', 'aa-comma-style': 'always' }, fix: true })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(1); await lintContext.applyFixes(); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(0); const actualSrc = fs.readFileSync(`${project1.rootDir}/source/aa-style-temp.brs`).toString(); const expectedSrc = fs.readFileSync(`${project1.rootDir}/source/aa-style-always.brs`).toString(); (0, chai_1.expect)(actualSrc).to.equal(expectedSrc); }); it('add missing aa comma, no dangling', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/aa-style-temp.brs'], rules: { 'named-function-style': 'off', 'anon-function-style': 'off', 'no-print': 'off', 'aa-comma-style': 'no-dangling' }, fix: true })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(1); await lintContext.applyFixes(); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(0); const actualSrc = fs.readFileSync(`${project1.rootDir}/source/aa-style-temp.brs`).toString(); const expectedSrc = fs.readFileSync(`${project1.rootDir}/source/aa-style-nodangling.brs`).toString(); (0, chai_1.expect)(actualSrc).to.equal(expectedSrc); }); it('adds eol last', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/no-eol-last-temp.brs'], rules: { 'eol-last': 'always' }, fix: true })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(1); await lintContext.applyFixes(); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(0); const actualSrc = fs.readFileSync(`${project1.rootDir}/source/no-eol-last-temp.brs`).toString(); const expectedSrc = fs.readFileSync(`${project1.rootDir}/source/eol-last.brs`).toString(); (0, chai_1.expect)(actualSrc).to.equal(expectedSrc); }); it('adds eol last to single line file', async () => { const diagnostics = await linter.run(Object.assign(Object.assign({}, project1), { files: ['source/single-line-temp.brs'], rules: { 'eol-last': 'always' }, fix: true })); const actual = (0, testHelpers_spec_1.fmtDiagnostics)(diagnostics); const expected = []; (0, chai_1.expect)(actual).deep.equal(expected); (0, chai_1.expect)(lintContext.pendingFixes.size).equals(1); await lintContext.applyFixes();