mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-20 02:08:57 +00:00
Add type-alias-readonlydeep rule and make ducks mostly immutable
This commit is contained in:
58
.eslint/rules/type-alias-readonlydeep.js
Normal file
58
.eslint/rules/type-alias-readonlydeep.js
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
function isReadOnlyDeep(node, scope) {
|
||||
if (node.type !== 'TSTypeReference') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let reference = scope.references.find(reference => {
|
||||
return reference.identifier === node.typeName;
|
||||
});
|
||||
|
||||
let variable = reference.resolved;
|
||||
if (variable == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let defs = variable.defs;
|
||||
if (defs.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let [def] = defs;
|
||||
|
||||
return (
|
||||
def.type === 'ImportBinding' &&
|
||||
def.parent.type === 'ImportDeclaration' &&
|
||||
def.parent.source.type === 'Literal' &&
|
||||
def.parent.source.value === 'type-fest'
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {import("eslint").Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
hasSuggestions: false,
|
||||
fixable: false,
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
TSTypeAliasDeclaration(node) {
|
||||
let scope = context.getScope(node);
|
||||
|
||||
if (isReadOnlyDeep(node.typeAnnotation, scope)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: node.id,
|
||||
message:
|
||||
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
79
.eslint/rules/type-alias-readonlydeep.test.js
Normal file
79
.eslint/rules/type-alias-readonlydeep.test.js
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
const rule = require('./type-alias-readonlydeep');
|
||||
const RuleTester = require('eslint').RuleTester;
|
||||
|
||||
// avoid triggering mocha's global leak detection
|
||||
require('@typescript-eslint/parser');
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
parser: require.resolve('@typescript-eslint/parser'),
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
},
|
||||
});
|
||||
|
||||
ruleTester.run('type-alias-readonlydeep', rule, {
|
||||
valid: [
|
||||
{
|
||||
code: `import type { ReadonlyDeep } from "type-fest"; type Foo = ReadonlyDeep<{}>`,
|
||||
},
|
||||
{
|
||||
code: `import { ReadonlyDeep } from "type-fest"; type Foo = ReadonlyDeep<{}>`,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: `type Foo = {}`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
|
||||
type: 'Identifier',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `type Foo = Bar<{}>`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
|
||||
type: 'Identifier',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `type Foo = ReadonlyDeep<{}>`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
|
||||
type: 'Identifier',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `interface ReadonlyDeep<T> {}; type Foo = ReadonlyDeep<{}>`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
|
||||
type: 'Identifier',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `import type { ReadonlyDeep } from "foo"; type Foo = ReadonlyDeep<{}>`,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'Type aliases must be wrapped with ReadonlyDeep from type-fest',
|
||||
type: 'Identifier',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
Reference in New Issue
Block a user