From 4fc9793cae3563945408283c74d6298f96d7e95e Mon Sep 17 00:00:00 2001 From: Jamie Kyle <113370520+jamiebuilds-signal@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:55:09 -0700 Subject: [PATCH] Add license-comments eslint rule --- .eslint/rules/license-comments.js | 73 ++++++++++++++++++++++++++ .eslintrc.js | 1 + eslint-local-rules.js | 1 + ts/state/smart/MessageSearchResult.tsx | 2 +- 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 .eslint/rules/license-comments.js diff --git a/.eslint/rules/license-comments.js b/.eslint/rules/license-comments.js new file mode 100644 index 0000000000..c5554a0414 --- /dev/null +++ b/.eslint/rules/license-comments.js @@ -0,0 +1,73 @@ +// Copyright 2025 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only +const COMMENT_LINE_1_EXACT = /^ Copyright \d{4} Signal Messenger, LLC$/; +const COMMENT_LINE_2_EXACT = /^ SPDX-License-Identifier: AGPL-3.0-only$/; + +const COMMENT_LINE_1_LOOSE = /Copyright (\d{4}) Signal Messenger, LLC/; +const COMMENT_LINE_2_LOOSE = /SPDX-License-Identifier: AGPL-3.0-only/; + +/** @type {import("eslint").Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + hasSuggestions: false, + fixable: true, + schema: [], + }, + create(context) { + return { + Program(node) { + let comment1 = node.comments.at(0); + let comment2 = node.comments.at(1); + + if ( + comment1?.type === 'Line' && + comment2?.type === 'Line' && + COMMENT_LINE_1_EXACT.test(comment1.value) && + COMMENT_LINE_2_EXACT.test(comment2.value) + ) { + return; + } + + context.report({ + node, + message: 'Missing license comment', + + fix(fixer) { + let year = null; + let remove = []; + + for (let comment of node.comments) { + let match1 = comment.value.match(COMMENT_LINE_1_LOOSE); + let match2 = comment.value.match(COMMENT_LINE_2_LOOSE); + + if (match1 != null) { + year = match1[1]; + } + + if (match1 != null || match2 != null) { + remove.push(comment); + } + } + + year ??= new Date().getFullYear().toString(); + + let insert = + `// Copyright ${year} Signal Messenger, LLC\n` + + '// SPDX-License-Identifier: AGPL-3.0-only\n'; + + return [ + fixer.replaceTextRange([0, 0], insert), + ...remove.map(comment => { + return fixer.replaceTextRange( + [comment.range[0], comment.range[1]], + '' + ); + }), + ]; + }, + }); + }, + }; + }, +}; diff --git a/.eslintrc.js b/.eslintrc.js index 837b7082e9..c52d76b4be 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -179,6 +179,7 @@ const rules = { additionalHooks: '^(useSpring|useSprings)$', }, ], + 'local-rules/license-comments': 'error', }; const typescriptRules = { diff --git a/eslint-local-rules.js b/eslint-local-rules.js index 1259cee9a2..4d4aafdb4d 100644 --- a/eslint-local-rules.js +++ b/eslint-local-rules.js @@ -3,5 +3,6 @@ /* eslint-disable global-require */ module.exports = { + 'license-comments': require('./.eslint/rules/license-comments'), 'type-alias-readonlydeep': require('./.eslint/rules/type-alias-readonlydeep'), }; diff --git a/ts/state/smart/MessageSearchResult.tsx b/ts/state/smart/MessageSearchResult.tsx index 3cc24e59d1..0e2d8414af 100644 --- a/ts/state/smart/MessageSearchResult.tsx +++ b/ts/state/smart/MessageSearchResult.tsx @@ -1,5 +1,5 @@ // Copyright 2019 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-onlyå +// SPDX-License-Identifier: AGPL-3.0-only import React, { memo } from 'react'; import { useSelector } from 'react-redux'; import { MessageSearchResult } from '../../components/conversationList/MessageSearchResult';