Skip to content

Commit 6e52074

Browse files
Lightning00BladeDevtools-frontend LUCI CQ
authored and
Devtools-frontend LUCI CQ
committed
[eslint] Migrate rules to TypeScript
Migrate l10n rules and their helpers Bug: 397586315 Change-Id: If425e0aefb990498337a6cd056d560f7d3a975d1 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6448905 Commit-Queue: Danil Somsikov <dsv@chromium.org> Commit-Queue: Nikolay Vitkov <nvitkov@chromium.org> Reviewed-by: Danil Somsikov <dsv@chromium.org> Auto-Submit: Nikolay Vitkov <nvitkov@chromium.org>
1 parent 3b38600 commit 6e52074

25 files changed

+838
-742
lines changed

scripts/eslint_rules/lib/enforce-default-import-name.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import type {TSESTree} from '@typescript-eslint/utils';
65
import path from 'path';
76

8-
import {createRule} from './tsUtils.ts';
7+
import {createRule, isStarAsImportSpecifier} from './tsUtils.ts';
98

109
// Define the structure of the options expected by the rule.
1110
type RuleOptions = [{
@@ -15,13 +14,6 @@ type RuleOptions = [{
1514

1615
// Define the message IDs used by the rule.
1716
type MessageIds = 'invalidName';
18-
type SpecifierNamespaceArray = [TSESTree.ImportNamespaceSpecifier];
19-
/**
20-
* Checks if the import specifiers represent a namespace import (`import * as Name`).
21-
*/
22-
function isStarAsImportSpecifier(specifiers: TSESTree.ImportClause[]): specifiers is SpecifierNamespaceArray {
23-
return specifiers.length === 1 && specifiers[0].type === 'ImportNamespaceSpecifier';
24-
}
2517

2618
export default createRule<RuleOptions, MessageIds>({
2719
name: 'enforce-default-import-name',

scripts/eslint_rules/lib/l10n-filename-matches.js

Lines changed: 0 additions & 112 deletions
This file was deleted.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2021 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import type {TSESTree} from '@typescript-eslint/utils';
6+
import * as path from 'path';
7+
8+
import {isModuleScope} from './l10n-helper.ts';
9+
import {createRule} from './tsUtils.ts';
10+
11+
type CallExpression = TSESTree.CallExpression;
12+
// True iff the callExpression is `i18n.i18n.registerUIStrings`.
13+
function isRegisterUIStringsCall(callExpression: CallExpression): boolean {
14+
if (callExpression.callee?.type !== 'MemberExpression') {
15+
return false;
16+
}
17+
const callee = callExpression.callee;
18+
19+
if (callee.property?.type !== 'Identifier' || callee.property?.name !== 'registerUIStrings') {
20+
return false;
21+
}
22+
23+
if (callee.object?.type !== 'MemberExpression') {
24+
return false;
25+
}
26+
const calleeObject = callee.object;
27+
28+
if (calleeObject.property?.type !== 'Identifier' || calleeObject.property?.name !== 'i18n') {
29+
return false;
30+
}
31+
32+
if (calleeObject.object?.type !== 'Identifier' || calleeObject.object?.name !== 'i18n') {
33+
return false;
34+
}
35+
return true;
36+
}
37+
38+
type Options = [
39+
{
40+
rootFrontendDirectory?: string,
41+
},
42+
];
43+
44+
type MessageIds = 'pathMismatch';
45+
46+
export default createRule<Options, MessageIds>({
47+
name: 'l10n-filename-matches',
48+
meta: {
49+
type: 'problem',
50+
docs: {
51+
description: 'i18n.i18n.registerUIStrings must receive the current file\'s path as the first argument',
52+
category: 'Possible Errors',
53+
},
54+
fixable: 'code',
55+
schema: [
56+
{
57+
type: 'object',
58+
properties: {
59+
rootFrontendDirectory: {
60+
type: 'string',
61+
},
62+
},
63+
additionalProperties: false,
64+
},
65+
],
66+
messages: {
67+
// Using a generic message ID as the message itself is dynamic
68+
pathMismatch:
69+
'First argument to \'registerUIStrings\' call must be \'{{expectedPath}}\' or the ModuleUIStrings.(js|ts)',
70+
},
71+
},
72+
defaultOptions: [{}],
73+
create: function(context) {
74+
const filename = context.filename;
75+
76+
let frontEndDirectory = '';
77+
if (context.options?.[0]?.rootFrontendDirectory) {
78+
frontEndDirectory = context.options[0].rootFrontendDirectory;
79+
}
80+
if (!frontEndDirectory) {
81+
throw new Error(
82+
'\'rootFrontendDirectory\' option must be provided for the l10n-filename-matches rule.',
83+
);
84+
}
85+
return {
86+
CallExpression(node) {
87+
if (!isModuleScope(context, node) || !isRegisterUIStringsCall(node)) {
88+
return;
89+
}
90+
91+
// Do nothing if there are no arguments or the first argument is not a string literal we
92+
// can check.
93+
const firstArgument = node.arguments[0];
94+
if (node.arguments.length === 0 || !firstArgument || firstArgument.type !== 'Literal' ||
95+
typeof firstArgument.value !== 'string') {
96+
return;
97+
}
98+
99+
const currentSourceFile = path.resolve(filename);
100+
const currentFileRelativeToFrontEnd = path.relative(
101+
frontEndDirectory,
102+
currentSourceFile,
103+
);
104+
105+
const currentModuleDirectory = path.dirname(currentSourceFile);
106+
const allowedPathArguments = [
107+
currentSourceFile,
108+
path.join(currentModuleDirectory, 'ModuleUIStrings.js'),
109+
path.join(currentModuleDirectory, 'ModuleUIStrings.ts'),
110+
];
111+
112+
const actualPath = path.join(
113+
frontEndDirectory,
114+
`${firstArgument.value}`,
115+
);
116+
117+
if (!allowedPathArguments.includes(actualPath)) {
118+
const newFileName = currentFileRelativeToFrontEnd.replace(/\\/g, '/');
119+
context.report({
120+
node,
121+
messageId: 'pathMismatch',
122+
data: {
123+
expectedPath: newFileName,
124+
},
125+
fix(fixer) {
126+
return fixer.replaceText(
127+
firstArgument,
128+
`'${newFileName}'`,
129+
);
130+
},
131+
});
132+
}
133+
},
134+
};
135+
},
136+
});

scripts/eslint_rules/lib/l10n-helper.js

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2021 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import type {TSESLint, TSESTree} from '@typescript-eslint/utils';
6+
7+
type RuleContext = TSESLint.RuleContext<string, unknown[]>;
8+
// Type alias for AST Node, using TSESTree for better compatibility with @typescript-eslint
9+
type Node = TSESTree.Node;
10+
type Identifier = TSESTree.Identifier;
11+
type VariableDeclarator = TSESTree.VariableDeclarator;
12+
13+
/**
14+
* Checks if a node is an Identifier whose name starts with "UIStrings".
15+
*/
16+
export function isUIStringsIdentifier(node: Node): node is Identifier&{
17+
name: `UIStrings${string}`,
18+
}
19+
{
20+
return node.type === 'Identifier' && node.name.startsWith('UIStrings');
21+
}
22+
/**
23+
* Checks if a node is within the module scope.
24+
*/
25+
export function isModuleScope(context: RuleContext, node: Node): boolean {
26+
// Prefer context.sourceCode, fallback to getSourceCode() for compatibility
27+
const sourceCode = context.sourceCode;
28+
// Prefer sourceCode.getScope if available
29+
const scope = sourceCode.getScope(node);
30+
return scope.type === 'module';
31+
}
32+
33+
/**
34+
* Checks if a VariableDeclarator is defining a UIStrings object
35+
* (module scope, identifier starts with UIStrings, initializer is a TSAsExpression).
36+
*/
37+
export function isUIStringsVariableDeclarator(context: RuleContext, variableDeclarator: VariableDeclarator): boolean {
38+
if (!isModuleScope(context, variableDeclarator)) {
39+
return false;
40+
}
41+
42+
if (!isUIStringsIdentifier(variableDeclarator.id)) {
43+
return false;
44+
}
45+
46+
// Check if the initializer exists and is a TSAsExpression
47+
return variableDeclarator.init?.type === 'TSAsExpression';
48+
}

0 commit comments

Comments
 (0)