@slippy-lint/slippy
Version:
A simple but powerful linter for Solidity
260 lines • 11.6 kB
JavaScript
import { Parser } from "@nomicfoundation/slang/parser";
import { TerminalKind } from "@nomicfoundation/slang/cst";
const disableNextLineMarker = "slippy-disable-next-line";
const disableLineMarker = "slippy-disable-line";
const disablePreviousLineMarker = "slippy-disable-previous-line";
const disableMarker = "slippy-disable";
const enableMarker = "slippy-enable";
export function filterByCommentDirectives(content, diagnostics, file, languageVersion) {
const directives = extractCommentDirectives(content, languageVersion);
const diagnosticsAndStatus = diagnostics.map((diagnostic) => {
return {
diagnostic,
disabledBy: null,
};
});
for (const diagnosticAndStatus of diagnosticsAndStatus) {
for (const directive of directives) {
if (directive.marker === "slippy-disable-next-line" ||
directive.marker === "slippy-disable-line" ||
directive.marker === "slippy-disable-previous-line") {
if (diagnosticAndStatus.diagnostic.line === directive.disabledLine) {
if (directive.disabledRules.length === 0 ||
(diagnosticAndStatus.diagnostic.rule !== null &&
directive.disabledRules.includes(diagnosticAndStatus.diagnostic.rule))) {
diagnosticAndStatus.disabledBy = directive;
}
}
}
else if (directive.marker === "slippy-disable") {
if (diagnosticAndStatus.diagnostic.line > directive.endLine ||
(diagnosticAndStatus.diagnostic.line === directive.endLine &&
diagnosticAndStatus.diagnostic.column >= directive.endColumn)) {
if (directive.disabledRules.length === 0 ||
(diagnosticAndStatus.diagnostic.rule !== null &&
directive.disabledRules.includes(diagnosticAndStatus.diagnostic.rule))) {
diagnosticAndStatus.disabledBy = directive;
}
}
}
else if (directive.marker === "slippy-enable") {
if (diagnosticAndStatus.diagnostic.line > directive.endLine ||
(diagnosticAndStatus.diagnostic.line === directive.endLine &&
diagnosticAndStatus.diagnostic.column >= directive.endColumn)) {
if (directive.enabledRules.length === 0 ||
(diagnosticAndStatus.diagnostic.rule !== null &&
directive.enabledRules.includes(diagnosticAndStatus.diagnostic.rule))) {
diagnosticAndStatus.disabledBy = null;
}
}
}
}
}
const filteredDiagnostics = diagnosticsAndStatus.flatMap((diagnosticAndStatus) => {
if (diagnosticAndStatus.disabledBy === null) {
return [diagnosticAndStatus.diagnostic];
}
return [];
});
const usedDirectives = new Map();
for (const diagnosticAndStatus of diagnosticsAndStatus) {
if (diagnosticAndStatus.disabledBy !== null &&
diagnosticAndStatus.diagnostic.rule !== null) {
const usedBy = usedDirectives.get(diagnosticAndStatus.disabledBy) ?? [];
usedBy.push(diagnosticAndStatus.diagnostic.rule);
usedDirectives.set(diagnosticAndStatus.disabledBy, usedBy);
}
}
const usedEnableDirectives = new Map();
for (const [i, disableDirective] of directives.entries()) {
if (disableDirective.marker !== "slippy-disable") {
continue;
}
if (disableDirective.disabledRules.length === 0) {
const allEnabledRules = new Set();
for (const enableDirective of directives.slice(i + 1)) {
if (enableDirective.marker !== "slippy-enable") {
continue;
}
if (enableDirective.enabledRules.length === 0) {
usedEnableDirectives.set(enableDirective, []);
break;
}
for (const rule of enableDirective.enabledRules) {
if (!allEnabledRules.has(rule)) {
allEnabledRules.add(rule);
const enabledRules = usedEnableDirectives.get(enableDirective) ?? [];
enabledRules.push(rule);
usedEnableDirectives.set(enableDirective, enabledRules);
}
}
}
}
else {
const disabledRules = new Set(disableDirective.disabledRules);
for (const enableDirective of directives.slice(i + 1)) {
if (disabledRules.size === 0) {
break;
}
if (enableDirective.marker !== "slippy-enable") {
continue;
}
if (enableDirective.enabledRules.length === 0) {
usedEnableDirectives.set(enableDirective, []);
break;
}
for (const rule of enableDirective.enabledRules) {
if (disabledRules.has(rule)) {
disabledRules.delete(rule);
const enabledRules = usedEnableDirectives.get(enableDirective) ?? [];
enabledRules.push(rule);
usedEnableDirectives.set(enableDirective, enabledRules);
}
}
}
}
}
for (const directive of directives) {
if (directive.marker === "slippy-enable") {
const usedBy = usedEnableDirectives.get(directive);
if (directive.enabledRules.length === 0) {
if (usedBy === undefined) {
filteredDiagnostics.push({
sourceId: file.id,
line: directive.textRange.start.line,
column: directive.textRange.start.column,
rule: null,
message: `Unused ${directive.marker} directive (no matching ${disableMarker} directives were found)`,
severity: "warn",
});
continue;
}
}
else {
const unusedRules = directive.enabledRules.filter((rule) => !(usedBy ?? []).includes(rule));
if (unusedRules.length === 0) {
continue;
}
let rulesFragment = `'${unusedRules[0]}'`;
for (let i = 1; i + 1 < unusedRules.length; i++) {
rulesFragment += `, '${unusedRules[i]}'`;
}
if (unusedRules.length > 1) {
rulesFragment += ` or '${unusedRules[unusedRules.length - 1]}'`;
}
filteredDiagnostics.push({
sourceId: file.id,
line: directive.textRange.start.line,
column: directive.textRange.start.column,
rule: null,
message: `Unused ${directive.marker} directive (no matching ${disableMarker} directives were found for ${rulesFragment})`,
severity: "warn",
});
}
continue;
}
const usedBy = usedDirectives.get(directive) ?? [];
if (directive.disabledRules.length === 0) {
if (usedBy.length === 0) {
filteredDiagnostics.push({
sourceId: file.id,
line: directive.textRange.start.line,
column: directive.textRange.start.column,
rule: null,
message: `Unused ${directive.marker} directive (no problems were reported)`,
severity: "warn",
});
continue;
}
}
else {
const unusedRules = directive.disabledRules.filter((rule) => !usedBy.includes(rule));
if (unusedRules.length > 0) {
let rulesFragment = `'${unusedRules[0]}'`;
for (let i = 1; i + 1 < unusedRules.length; i++) {
rulesFragment += `, '${unusedRules[i]}'`;
}
if (unusedRules.length > 1) {
rulesFragment += ` or '${unusedRules[unusedRules.length - 1]}'`;
}
filteredDiagnostics.push({
sourceId: file.id,
line: directive.textRange.start.line,
column: directive.textRange.start.column,
rule: null,
message: `Unused ${directive.marker} directive (no problems were reported from ${rulesFragment})`,
severity: "warn",
});
}
}
}
return filteredDiagnostics;
}
export function extractCommentDirectives(content, languageVersion) {
const directives = [];
const parser = Parser.create(languageVersion);
const parseOutput = parser.parseFileContents(content);
const cursor = parseOutput.createTreeCursor();
while (cursor.goToNextTerminalWithKinds([
TerminalKind.SingleLineComment,
TerminalKind.MultiLineComment,
])) {
let commentText = cursor.node.unparse().slice(2).trim();
if (cursor.node.kind === TerminalKind.MultiLineComment) {
commentText = commentText.slice(0, -2).trim();
}
const firstSpace = /\s/.exec(commentText)?.index;
const args = firstSpace === undefined
? []
: commentText
.slice(firstSpace + 1)
.trim()
.split(",")
.map((rule) => rule.trim())
.filter((rule) => rule.length > 0);
if (commentText.startsWith(disableNextLineMarker)) {
directives.push({
marker: "slippy-disable-next-line",
disabledLine: cursor.textRange.end.line + 1,
disabledRules: args,
textRange: cursor.textRange,
});
}
else if (commentText.startsWith(disableLineMarker)) {
directives.push({
marker: "slippy-disable-line",
disabledLine: cursor.textRange.start.line,
disabledRules: args,
textRange: cursor.textRange,
});
}
else if (commentText.startsWith(disablePreviousLineMarker)) {
directives.push({
marker: "slippy-disable-previous-line",
disabledLine: cursor.textRange.start.line - 1,
disabledRules: args,
textRange: cursor.textRange,
});
}
else if (commentText.startsWith(disableMarker)) {
directives.push({
marker: "slippy-disable",
endLine: cursor.textRange.end.line,
endColumn: cursor.textRange.end.column,
disabledRules: args,
textRange: cursor.textRange,
});
}
else if (commentText.startsWith(enableMarker)) {
directives.push({
marker: "slippy-enable",
endLine: cursor.textRange.end.line,
endColumn: cursor.textRange.end.column,
enabledRules: args,
textRange: cursor.textRange,
});
}
}
return directives;
}
//# sourceMappingURL=comment-directives.js.map