Add DiagnosticsDuplicateHelper to assist in creation of duplicate diagnostics
ADD
packages/@romejs/diagnostics/DiagnosticsDuplicateHelper.ts
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import DiagnosticsProcessor from './DiagnosticsProcessor';
+import {DiagnosticLocation, DiagnosticDescription} from './types';
+import {buildDuplicateLocationAdvice} from './helpers';
+
+type DescriptionFactory = (key: string) => DiagnosticDescription;
+
+export class DiagnosticsDuplicateHelper {
+ constructor(
+ processor: DiagnosticsProcessor,
+ descriptionFactory: DescriptionFactory,
+ ) {
+ this.processor = processor;
+ this.descriptionFactory = descriptionFactory;
+ this.locations = new Map();
+ }
+
+ descriptionFactory: DescriptionFactory;
+ locations: Map<string, Array<DiagnosticLocation>>;
+ processor: DiagnosticsProcessor;
+
+ addLocation(key: string, location: undefined | DiagnosticLocation) {
+ if (location === undefined) {
+ // This shouldn't really happen. We'll normally be passing in `node.loc` into here
+ // which can possibly be `undefined`. We don't want to error though in case it was
+ // inserted dynamically as we'd have nowhere to point to.
+ return;
+ }
+
+ let locations = this.locations.get(key);
+ if (locations === undefined) {
+ locations = [];
+ this.locations.set(key, locations);
+ }
+ locations.push(location);
+ }
+
+ process() {
+ for (const [key, locations] of this.locations) {
+ if (locations.length === 1) {
+ continue;
+ }
+
+ const description = this.descriptionFactory(key);
+ const firstLocation = locations[0];
+ const restLocations = locations.slice(1);
+
+ this.processor.addDiagnostic({
+ location: firstLocation,
+ description: {
+ ...description,
+ advice: [
+ ...(description.advice || []),
+ ...buildDuplicateLocationAdvice(restLocations),
+ ],
+ },
+ });
+ }
+ }
+}

MODIFY
packages/@romejs/diagnostics/descriptions.ts
packages/@romejs/diagnostics/descriptions.ts
@@ -412,13 +412,9 @@ export const descriptions = createMessages(
message: "Use the rest parameters instead of 'arguments'",
},
- DUPLICATE_REGEX_GROUP_NAME: (
- name: string,
- locations: Array<undefined | DiagnosticLocation>,
- ) => ({
+ DUPLICATE_REGEX_GROUP_NAME: (name: string) => ({
category: 'lint/noDuplicateGroupNamesInRegularExpressions',
message: `Duplicate group name <emphasis>${name}</emphasis> in regular expression`,
- advice: buildDuplicateLocationAdvice(locations),
}),
NO_REFERENCE_TO_NON_EXISTING_GROUP: (name: string) => ({

MODIFY
packages/@romejs/diagnostics/helpers.ts
packages/@romejs/diagnostics/helpers.ts
@@ -101,6 +101,22 @@ export function buildSuggestionAdvice(
return advice;
}
+// Sometimes we'll have big blobs of JS in a diagnostic when we'll only show a snippet. This method pads it out then truncates the rest for efficient transmission. We will have crappy ANSI formatting in the console and elsewhere but for places where we need to truncate we probably don't care (generated code).
+export function truncateSourceText(
+ code: string,
+ start: Position,
+ end: Position,
+): string {
+ const lines = code.split(NEWLINE);
+
+ // Pad the starting and ending lines by 10
+ const fromLine = Math.max(get1(start.line) - 10, 0);
+ const toLine = Math.max(get1(end.line) + 10, lines.length);
+
+ const capturedLines = lines.slice(fromLine, toLine);
+ return '\n'.repeat(fromLine) + capturedLines.join('\n');
+}
+
export function buildDuplicateLocationAdvice(
locations: Array<undefined | DiagnosticLocation>,
): DiagnosticAdvice {
@@ -128,19 +144,3 @@ export function buildDuplicateLocationAdvice(
...locationAdvice,
];
}
-
-// Sometimes we'll have big blobs of JS in a diagnostic when we'll only show a snippet. This method pads it out then truncates the rest for efficient transmission. We will have crappy ANSI formatting in the console and elsewhere but for places where we need to truncate we probably don't care (generated code).
-export function truncateSourceText(
- code: string,
- start: Position,
- end: Position,
-): string {
- const lines = code.split(NEWLINE);
-
- // Pad the starting and ending lines by 10
- const fromLine = Math.max(get1(start.line) - 10, 0);
- const toLine = Math.max(get1(end.line) + 10, lines.length);
-
- const capturedLines = lines.slice(fromLine, toLine);
- return '\n'.repeat(fromLine) + capturedLines.join('\n');
-}

MODIFY
packages/@romejs/js-analysis/api/check.ts
packages/@romejs/js-analysis/api/check.ts
@@ -27,7 +27,7 @@ export default async function check(opts: {
project: opts.project,
});
resolveGraph(hub);
- return hub.context.diagnostics;
+ return hub.context.diagnostics.getDiagnostics();
}
function isError(t: undefined | T): boolean {
@@ -134,5 +134,5 @@ function resolveGraph(hub: Hub): Diagnostics {
}
}
- return context.diagnostics;
+ return context.diagnostics.getDiagnostics();
}

MODIFY
packages/@romejs/js-compiler/api/analyzeDependencies/index.ts
packages/@romejs/js-compiler/api/analyzeDependencies/index.ts
@@ -267,7 +267,7 @@ export default async function analyzeDependencies(
dependencies,
importFirstUsage,
syntax: ast.syntax,
- diagnostics: [...ast.diagnostics, ...context.diagnostics],
+ diagnostics: [...ast.diagnostics, ...context.diagnostics.getDiagnostics()],
};
analyzeCache.set(query, res);
return res;

MODIFY
packages/@romejs/js-compiler/api/lint.ts
packages/@romejs/js-compiler/api/lint.ts
@@ -75,7 +75,7 @@ export default async function lint(req: FormatRequest): Promise<LintResult> {
const result: LintResult = {
suppressions: context.suppressions,
- diagnostics: [...ast.diagnostics, ...context.diagnostics],
+ diagnostics: [...ast.diagnostics, ...context.diagnostics.getDiagnostics()],
src: formattedCode,
};
lintCache.set(query, result);

MODIFY
packages/@romejs/js-compiler/lib/Context.ts
packages/@romejs/js-compiler/lib/Context.ts
@@ -19,11 +19,11 @@ import {
} from '@romejs/js-compiler';
import {
Diagnostic,
- Diagnostics,
DiagnosticOrigin,
DiagnosticDescription,
DiagnosticSuppressions,
DiagnosticCategory,
+ DiagnosticsProcessor,
} from '@romejs/diagnostics';
import Record from './Record';
import {RootScope} from '../scope/Scope';
@@ -66,13 +66,14 @@ export default class Context {
const {suppressions, diagnostics} = extractSuppressionsFromProgram(ast);
this.suppressions = suppressions;
- this.diagnostics = diagnostics;
+ this.diagnostics = new DiagnosticsProcessor();
+ this.diagnostics.addDiagnostics(diagnostics);
}
sourceType: ConstSourceType;
cacheDependencies: Set<string>;
records: Array<Record>;
- diagnostics: Diagnostics;
+ diagnostics: DiagnosticsProcessor;
suppressions: DiagnosticSuppressions;
rootScope: undefined | RootScope;
@@ -154,10 +155,6 @@ export default class Context {
this.records.push(record);
}
- addDiagnostics(diagnostics: Diagnostics) {
- this.diagnostics = [...this.diagnostics, ...diagnostics];
- }
-
addLocDiagnostic(
loc: undefined | SourceLocation,
description: DiagnosticDescription,
@@ -177,7 +174,7 @@ export default class Context {
);
}
- this.diagnostics.push({
+ this.diagnostics.addDiagnostic({
...diag,
description,
location: {

MODIFY
packages/@romejs/js-compiler/methods/transform.ts
packages/@romejs/js-compiler/methods/transform.ts
@@ -74,7 +74,10 @@ export default async function transform(
const res: TransformResult = {
suppressions: context.suppressions,
- diagnostics: [...prevStageDiagnostics, ...context.diagnostics],
+ diagnostics: [
+ ...prevStageDiagnostics,
+ ...context.diagnostics.getDiagnostics(),
+ ],
cacheDependencies: [
...prevStageCacheDeps,
...context.getCacheDependencies(),

MODIFY
packages/@romejs/js-compiler/transforms/lint/noDuplicateGroupNamesInRegularExpressions.ts
packages/@romejs/js-compiler/transforms/lint/noDuplicateGroupNamesInRegularExpressions.ts
* LICENSE file in the root directory of this source tree.
*/
-import {AnyNode, RegExpGroupCapture} from '@romejs/js-ast';
+import {AnyNode} from '@romejs/js-ast';
import {Path} from '@romejs/js-compiler';
import {descriptions} from '@romejs/diagnostics';
+import {
+ DiagnosticsDuplicateHelper,
+} from '@romejs/diagnostics/DiagnosticsDuplicateHelper';
export default {
name: 'noDuplicateGroupNamesInRegularExpressions',
@@ -15,40 +18,24 @@ export default {
const {context, node} = path;
if (node.type === 'RegExpSubExpression') {
- const groupUsage = new Map<string, Array<RegExpGroupCapture>>();
+ const duplicates = new DiagnosticsDuplicateHelper(
+ context.diagnostics,
+ descriptions.LINT.DUPLICATE_REGEX_GROUP_NAME,
+ );
for (const bodyItem of node.body) {
if (bodyItem.type === 'RegExpGroupCapture') {
const groupName = bodyItem.name;
if (groupName !== undefined) {
- let usages = groupUsage.get(groupName);
-
- if (usages === undefined) {
- usages = [];
- groupUsage.set(groupName, usages);
- }
- usages.push(bodyItem);
+ duplicates.addLocation(groupName, bodyItem.loc);
}
}
}
- for (const [name, usages] of groupUsage) {
- if (usages.length === 1) {
- continue;
+ duplicates.process();
}
- const firstUsage = usages[0];
-
- context.addNodeDiagnostic(
- firstUsage,
- descriptions.LINT.DUPLICATE_REGEX_GROUP_NAME(
- name,
- usages.slice(1).map((node) => node.loc),
- ),
- );
- }
- }
return node;
},
};

MODIFY
packages/@romejs/js-compiler/transforms/lint/noDuplicateKeys.ts
packages/@romejs/js-compiler/transforms/lint/noDuplicateKeys.ts
@@ -9,6 +9,9 @@ import {Path} from '@romejs/js-compiler';
import {ObjectProperty, ObjectMethod, SpreadProperty} from '@romejs/js-ast';
import {TransformExitResult} from '@romejs/js-compiler/types';
import {descriptions} from '@romejs/diagnostics';
+import {
+ DiagnosticsDuplicateHelper,
+} from '@romejs/diagnostics/DiagnosticsDuplicateHelper';
function extractPropertyKey(
node: ObjectProperty | ObjectMethod | SpreadProperty,
@@ -35,25 +38,22 @@ function extractPropertyKey(
export default {
name: 'noDuplicateKeys',
enter(path: Path): TransformExitResult {
- const {node} = path;
+ const {node, context} = path;
if (node.type === 'ObjectExpression') {
- const previousKeys = new Set();
+ const duplicates = new DiagnosticsDuplicateHelper(
+ context.diagnostics,
+ descriptions.LINT.NO_DUPLICATE_KEYS,
+ );
for (const prop of node.properties) {
const key = extractPropertyKey(prop);
-
if (key !== undefined) {
- if (previousKeys.has(key)) {
- path.context.addNodeDiagnostic(
- prop,
- descriptions.LINT.NO_DUPLICATE_KEYS(key),
- );
- }
-
- previousKeys.add(key);
+ duplicates.addLocation(key, prop.loc);
}
}
+
+ duplicates.process();
}
return node;

Add DiagnosticsDuplicateHelper to assist in creation of duplicate diagnostics