Skip to content

Commit

Permalink
feat(eslint-plugin): add no-typeof rule
Browse files Browse the repository at this point in the history
  • Loading branch information
splincode committed Jan 18, 2023
1 parent 926bb66 commit cb47af2
Show file tree
Hide file tree
Showing 17 changed files with 113 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics';
import {updateWorkspace} from '@schematics/angular/utility/workspace';
import {getPackageJsonDependency, getSourceFiles} from 'ng-morph';

import {tuiIsString} from '../../../../utils';
import {TuiSchema} from '../../../ng-add/schema';
import {isInvalidAngularJson} from '../../../utils/angular-json-manipulations';
import {getProjectTargetOptions} from '../../../utils/get-project-target-options';
Expand Down Expand Up @@ -73,7 +74,7 @@ export function migrateTaigaProprietaryIcons(options: TuiSchema): Rule {
const tdsSrc = `@taiga-ui/proprietary-tds-icons/src`;
const hasIcons = (targetOptions.assets as Asset[]).find(
asset => {
return typeof asset === `string`
return tuiIsString(asset)
? asset.includes(tdsSrc)
: asset?.input?.includes(tdsSrc);
},
Expand Down
3 changes: 2 additions & 1 deletion projects/cdk/schematics/utils/angular-json-manipulations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {getWorkspace, updateWorkspace} from '@schematics/angular/utility/workspa
import {addPackageJsonDependency} from 'ng-morph';

import {ALWAYS_FALSE_HANDLER, ALWAYS_TRUE_HANDLER} from '../../constants';
import {tuiIsString} from '../../utils';
import {TAIGA_VERSION} from '../ng-add/constants/versions';
import {TuiSchema} from '../ng-add/schema';
import {Asset} from '../ng-update/interfaces/asset';
Expand All @@ -25,7 +26,7 @@ export async function isInvalidAngularJson(tree: Tree): Promise<boolean> {

function hasTaigaIcons(assets: Asset[]): boolean {
return !!assets?.find(asset =>
typeof asset === `string`
tuiIsString(asset)
? asset.includes(`taiga-ui`)
: asset?.input?.includes(`taiga-ui`),
);
Expand Down
1 change: 1 addition & 0 deletions projects/cdk/utils/miscellaneous/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './flat-length';
export * from './get-original-array-from-query-list';
export * from './get-swipe-direction';
export * from './is-number';
export * from './is-object';
export * from './is-present';
export * from './is-string';
export * from './mark-control-as-touched-and-validate';
Expand Down
1 change: 1 addition & 0 deletions projects/cdk/utils/miscellaneous/is-number.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export function tuiIsNumber(value: unknown): value is number {
// eslint-disable-next-line @taiga-ui/no-typeof
return typeof value === `number`;
}
6 changes: 6 additions & 0 deletions projects/cdk/utils/miscellaneous/is-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function tuiIsObject<T extends Record<any, any> | object>(
value: unknown,
): value is NonNullable<T> {
// eslint-disable-next-line @taiga-ui/no-typeof
return typeof value === `object` && !!value;
}
1 change: 1 addition & 0 deletions projects/cdk/utils/miscellaneous/is-string.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export function tuiIsString(value: unknown): value is string {
// eslint-disable-next-line @taiga-ui/no-typeof
return typeof value === `string`;
}
3 changes: 2 additions & 1 deletion projects/cdk/utils/svg/svg-linear-gradient-processor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {SafeHtml} from '@angular/platform-browser';
import {tuiIsString} from '@taiga-ui/cdk/utils';

/**
* @description:
Expand All @@ -18,7 +19,7 @@ export function tuiSvgLinearGradientProcessor(
svg: SafeHtml | string,
salt?: number | string,
): SafeHtml | string {
if (typeof svg === `string`) {
if (tuiIsString(svg)) {
const uniqueIds = extractLinearGradientIdsFromSvg(svg);

return uniqueIds.reduce(
Expand Down
3 changes: 2 additions & 1 deletion projects/demo/webpack.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {tuiIsObject} from '@taiga-ui/cdk';
import {Configuration} from 'webpack';
import {merge} from 'webpack-merge';

Expand Down Expand Up @@ -42,7 +43,7 @@ const config: Configuration = {
export default (ngConfigs: Configuration): Configuration => {
const ngRules = [...(ngConfigs.module?.rules || [])].map(rule => {
if (
typeof rule === `object` &&
tuiIsObject(rule) &&
DONT_MUTATE_RAW_FILE_CONTENTS.some(
pattern => rule.test instanceof RegExp && rule.test?.test(pattern),
)
Expand Down
1 change: 1 addition & 0 deletions projects/eslint-plugin/configs/all.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"@taiga-ui/injection-token-description": "error",
"@taiga-ui/prefer-inject-decorator": "error",
"@taiga-ui/prefer-self-destroy-service": "error",
"@taiga-ui/no-typeof": "error",
"@taiga-ui/no-deep-imports": [
"error",
{
Expand Down
1 change: 1 addition & 0 deletions projects/eslint-plugin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ module.exports = {
'no-deep-imports': require('./rules/no-deep-imports'),
'prefer-inject-decorator': require('./rules/prefer-inject-decorator'),
'prefer-self-destroy-service': require('./rules/prefer-self-destroy-service'),
'no-typeof': require('./rules/no-typeof'),
},
};
69 changes: 69 additions & 0 deletions projects/eslint-plugin/rules/no-typeof.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* @type {import('eslint').Rule.RuleModule}
*/
module.exports = {
meta: {
type: 'problem',
schema: [],
},
create(context) {
return {
/**
* @type {import('eslint').Rule.Node}
* @return {*}
*/
"BinaryExpression[operator='==='][left.operator='typeof']"(node) {
markTypeOfString(context, node);
markTypeOfNumber(context, node);
markTypeOfObject(context, node);
},
};
},
};

/**
* @param {import('eslint').Rule.RuleContext} context
* @param {import('eslint').Rule.Node} node
*/
function markTypeOfString(context, node) {
if (checkRightOperandIs(node, `string`)) {
context.report({
node,
message: `Don't use "typeof value === 'string'" instead of tuiIsString(value)`,
});
}
}

/**
* @param {import('eslint').Rule.RuleContext} context
* @param {import('eslint').Rule.Node} node
*/
function markTypeOfNumber(context, node) {
if (checkRightOperandIs(node, `number`)) {
context.report({
node,
message: `Don't use "typeof value === 'number'" instead of tuiIsNumber(value)`,
});
}
}

/**
* @param {import('eslint').Rule.RuleContext} context
* @param {import('eslint').Rule.Node} node
*/
function markTypeOfObject(context, node) {
if (checkRightOperandIs(node, `object`)) {
context.report({
node,
message: `Don't use "typeof value === 'object'" instead of tuiIsObject(value)`,
});
}
}

/**
* @param {import('eslint').Rule.Node} node
* @param {string} type
*/
function checkRightOperandIs(node, type) {
return node?.right?.quasis?.[0]?.value?.raw === type || node?.right?.value === type;
}
14 changes: 7 additions & 7 deletions projects/icons/scripts/process-icons.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {tuiIsString} from '@taiga-ui/cdk';
import fs from 'fs';
import {parse} from 'path';

Expand Down Expand Up @@ -27,13 +28,12 @@ export function processIcons(files: string[], interceptor?: ContentInterceptor):

const wrapped = wrapIcon(src, name);

const final =
typeof wrapped === `string`
? `${wrapped.replace(
START,
`<svg xmlns="http://www.w3.org/2000/svg"><g id="${name}" xmlns="http://www.w3.org/2000/svg"><svg`,
)}</g></svg>`
: `<svg xmlns="http://www.w3.org/2000/svg" width="${wrapped.width}" height="${wrapped.height}">${wrapped.src}</svg>`;
const final = tuiIsString(wrapped)
? `${wrapped.replace(
START,
`<svg xmlns="http://www.w3.org/2000/svg"><g id="${name}" xmlns="http://www.w3.org/2000/svg"><svg`,
)}</g></svg>`
: `<svg xmlns="http://www.w3.org/2000/svg" width="${wrapped.width}" height="${wrapped.height}">${wrapped.src}</svg>`;

fs.writeFileSync(file, final);

Expand Down
6 changes: 2 additions & 4 deletions projects/kit/components/avatar/avatar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
Input,
} from '@angular/core';
import {SafeResourceUrl} from '@angular/platform-browser';
import {tuiDefaultProp, tuiRequiredSetter} from '@taiga-ui/cdk';
import {tuiDefaultProp, tuiIsString, tuiRequiredSetter} from '@taiga-ui/cdk';
import {tuiSizeBigger} from '@taiga-ui/core';
import {tuiStringHashToHsl} from '@taiga-ui/kit/utils/format';

Expand Down Expand Up @@ -61,9 +61,7 @@ export class TuiAvatarComponent {
}

get iconAvatar(): boolean {
return (
typeof this.avatarUrl === 'string' && !!this.avatarUrl?.startsWith('tuiIcon')
);
return tuiIsString(this.avatarUrl) && !!this.avatarUrl?.startsWith('tuiIcon');
}

get computedText(): string {
Expand Down
11 changes: 5 additions & 6 deletions projects/testing/cypress/snapshot/command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// <reference types="cypress" />

import {tuiIsObject, tuiIsString} from '@taiga-ui/cdk';
import {Options} from 'cypress-image-snapshot';
import {matchImageSnapshotCommand} from 'cypress-image-snapshot/command';

Expand Down Expand Up @@ -37,11 +37,10 @@ export function tuiAddMatchImageSnapshotCommand(
nameOrOptions: string | (Options & TuiSnapshotCommandOptions),
options?: Options & TuiSnapshotCommandOptions,
) => {
const name = typeof nameOrOptions === `string` ? nameOrOptions : undefined;
const overloadedOptions =
typeof nameOrOptions === `object` && !!nameOrOptions
? nameOrOptions
: options;
const name = tuiIsString(nameOrOptions) ? nameOrOptions : undefined;
const overloadedOptions = tuiIsObject(nameOrOptions)
? nameOrOptions
: options;

tuiWaitAllImgInside(prevSubject, overloadedOptions?.waitAllImages ?? true);

Expand Down
3 changes: 2 additions & 1 deletion scripts/shared/bump-tui-deps.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {tuiIsString} from '../../projects/cdk';
import {isTuiPackageName} from './is-tui-package-name';

export interface TuiBumpDepsOptions {
Expand All @@ -18,7 +19,7 @@ export function bumpTuiDeps({
const keys = Object.keys(deps).filter(key => isTuiPackageName(key, ignores));

for (const key of keys) {
if (typeof deps[key] === `string`) {
if (tuiIsString(deps[key])) {
deps[key] = isPeerDependency
? (deps[key] as string)?.replace(prevVersion, newVersion)
: `^${newVersion}`;
Expand Down
4 changes: 3 additions & 1 deletion scripts/shared/bump-tui-version-in-package-json.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {tuiIsString} from '../../projects/cdk';

export function bumpTuiVersionInPackageJson(
packageJson: Record<string, unknown>,
version: string,
): void {
if (`version` in packageJson && typeof packageJson[`version`] === `string`) {
if (`version` in packageJson && tuiIsString(packageJson[`version`])) {
packageJson[`version`] = version;
}
}
11 changes: 6 additions & 5 deletions scripts/shared/update-package-json-structure.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {tuiIsObject, tuiIsString} from '../../projects/cdk';
import {bumpTuiDeps} from './bump-tui-deps';
import {bumpTuiVersionInPackageJson} from './bump-tui-version-in-package-json';
import {isTuiPackageName} from './is-tui-package-name';
Expand All @@ -19,15 +20,15 @@ export function updatePackageJsonStructure({
}: UpdatePackageJsonOptions): void {
const {name, dependencies, peerDependencies, devDependencies, packages} = packageJson;

if (typeof name === `string` && isTuiPackageName(name, ignores)) {
if (tuiIsString(name) && isTuiPackageName(name, ignores)) {
bumpTuiVersionInPackageJson(packageJson, newVersion);
}

if (typeof dependencies === `object`) {
if (tuiIsObject(dependencies)) {
bumpTuiDeps({deps: dependencies, prevVersion, newVersion, ignores});
}

if (typeof peerDependencies === `object`) {
if (tuiIsObject(peerDependencies)) {
bumpTuiDeps({
deps: peerDependencies,
prevVersion,
Expand All @@ -37,11 +38,11 @@ export function updatePackageJsonStructure({
});
}

if (typeof devDependencies === `object`) {
if (tuiIsObject(devDependencies)) {
bumpTuiDeps({deps: devDependencies, prevVersion, newVersion, ignores});
}

if (isPackageLockFile && typeof packages === `object`) {
if (isPackageLockFile && tuiIsObject(packages)) {
for (const packageLockJson of Object.values(packages)) {
if (!isTuiPackageName(packageLockJson?.name, ignores)) {
continue;
Expand Down

0 comments on commit cb47af2

Please sign in to comment.