Skip to content
File

Blob: tools/custom-eslint-rules.mjs

javascript115 lines
1/**
2 * ESLint rule to require a Cloudflare copyright header at the top of every file.
3 *
4 * Requires files to start with:
5 * // Copyright (c) <year or year-range> Cloudflare, Inc.
6 * // Licensed under the Apache 2.0 license found in the LICENSE file or at:
7 * // https://opensource.org/licenses/Apache-2.0
8 */
9export const requireCopyrightHeader = {
10 meta: {
11 type: 'suggestion',
12 docs: {
13 description:
14 'Require a Cloudflare copyright header at the top of every file',
15 },
16 messages: {
17 missingHeader:
18 'File is missing the Cloudflare copyright header. Add the following at the top of the file:\n// Copyright (c) {{year}} Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n// https://opensource.org/licenses/Apache-2.0',
19 },
20 fixable: 'code',
21 schema: [],
22 },
23 create(context) {
24 return {
25 Program(node) {
26 const sourceText = context.sourceCode.getText();
27 const headerPattern =
28 /^\/\/ Copyright \(c\) \d{4}(-\d{4})? Cloudflare, Inc\.\n\/\/ Licensed under the Apache 2\.0 license found in the LICENSE file or at:\n\/\/ https:\/\/opensource\.org\/licenses\/Apache-2\.0/;
29 
30 if (!headerPattern.test(sourceText)) {
31 const year = new Date().getFullYear();
32 context.report({
33 node,
34 messageId: 'missingHeader',
35 data: { year: String(year) },
36 fix(fixer) {
37 const header =
38 `// Copyright (c) ${year} Cloudflare, Inc.\n` +
39 '// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n' +
40 '// https://opensource.org/licenses/Apache-2.0\n';
41 return fixer.insertTextBefore(node, header);
42 },
43 });
44 }
45 },
46 };
47 },
48};
49 
50/**
51 * ESLint rule to disallow re-exporting a namespace import as default.
52 *
53 * Disallows patterns like:
54 * import * as _default from 'some-module';
55 * export default _default;
56 *
57 * Instead, use:
58 * import { var1, var2 } from 'some-module';
59 * export * from 'some-module';
60 * export default { var1, var2 };
61 *
62 * @see https://github.com/cloudflare/workerd/issues/5844
63 */
64export const noExportDefaultOfImportStar = {
65 meta: {
66 type: 'problem',
67 docs: {
68 description:
69 'Disallow exporting a namespace import as the default export',
70 },
71 messages: {
72 noExportDefaultOfImportStar:
73 "Do not re-export a namespace import as default. Use 'import { var1, var2 } from \"{{source}}\"', 'export * from \"{{source}}\"', and 'export default { var1, var2 }' instead.",
74 },
75 schema: [],
76 },
77 create(context) {
78 const namespaceImports = new Map();
79 
80 return {
81 ImportDeclaration(node) {
82 // Look for: import * as X from '...'
83 for (const specifier of node.specifiers) {
84 if (specifier.type === 'ImportNamespaceSpecifier') {
85 namespaceImports.set(specifier.local.name, node.source.value);
86 }
87 }
88 },
89 
90 ExportDefaultDeclaration(node) {
91 // Look for: export default X (where X is an identifier)
92 if (node.declaration && node.declaration.type === 'Identifier') {
93 const exportedName = node.declaration.name;
94 const source = namespaceImports.get(exportedName);
95 
96 if (source !== undefined) {
97 context.report({
98 node,
99 messageId: 'noExportDefaultOfImportStar',
100 data: { source },
101 });
102 }
103 }
104 },
105 };
106 },
107};
108 
109export default {
110 rules: {
111 'require-copyright-header': requireCopyrightHeader,
112 'no-export-default-of-import-star': noExportDefaultOfImportStar,
113 },
114};