@slip-stream-kit/eslint-plugin 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4348,6 +4348,235 @@ var requireJsdocExample = {
4348
4348
  }
4349
4349
  };
4350
4350
 
4351
+ // src/rules/services-namespace-import/services-namespace-import.ts
4352
+ var DEFAULT_NAME = "service";
4353
+ var BARREL_FILE = /\/index\.[cm]?[jt]sx?$/;
4354
+ var SERVICES_SPECIFIER = /^\.{1,2}\/(?:.*\/)?services(?:\/index)?(?:\.[cm]?[jt]sx?)?$/;
4355
+ var isTypeOnly = (node) => {
4356
+ return node.importKind === "type";
4357
+ };
4358
+ var isValueBinding = (specifier) => {
4359
+ return specifier.type !== "ImportNamespaceSpecifier" && !isTypeOnly(specifier);
4360
+ };
4361
+ var importedName = (specifier) => {
4362
+ if (specifier.type === "ImportDefaultSpecifier") {
4363
+ return "default";
4364
+ }
4365
+ if (specifier.type === "ImportSpecifier") {
4366
+ return specifier.imported.type === "Identifier" ? specifier.imported.name : String(specifier.imported.value);
4367
+ }
4368
+ return specifier.local.name;
4369
+ };
4370
+ var isShadowedAt = (reference, name) => {
4371
+ let scope = reference.from;
4372
+ while (scope !== null && scope.type !== "module" && scope.type !== "global") {
4373
+ if (scope.set.has(name)) {
4374
+ return true;
4375
+ }
4376
+ scope = scope.upper;
4377
+ }
4378
+ return false;
4379
+ };
4380
+ var isRewritable = (reference, name) => {
4381
+ const identifier = reference.identifier;
4382
+ return identifier.type !== "JSXIdentifier" && identifier.parent?.type !== "ExportSpecifier" && !isShadowedAt(reference, name);
4383
+ };
4384
+ var referenceEdit = (reference, local, member) => {
4385
+ const identifier = reference.identifier;
4386
+ const parent = identifier.parent;
4387
+ if (parent?.type === "Property" && parent.shorthand && parent.value === identifier) {
4388
+ return { range: parent.range, text: `${local}: ${member}` };
4389
+ }
4390
+ return { range: identifier.range, text: member };
4391
+ };
4392
+ var rewriteBindings = (bindings, name) => {
4393
+ const references = bindings.flatMap(({ variable, member }) => {
4394
+ return variable.references.map((reference) => {
4395
+ return { reference, local: variable.name, member };
4396
+ });
4397
+ });
4398
+ const allRewritable = references.every(({ reference }) => {
4399
+ return isRewritable(reference, name);
4400
+ });
4401
+ if (!allRewritable) {
4402
+ return null;
4403
+ }
4404
+ return references.map(({ reference, local, member }) => {
4405
+ return referenceEdit(reference, local, member === "" ? name : `${name}.${member}`);
4406
+ });
4407
+ };
4408
+ var typeImportLine = (declaration, sourceCode) => {
4409
+ const names = declaration.specifiers.filter(isTypeOnly).map((specifier) => {
4410
+ return sourceCode.getText(specifier).replace(/^type\s+/, "");
4411
+ });
4412
+ return names.length === 0 ? null : `import type { ${names.join(", ")} } from ${sourceCode.getText(declaration.source)}`;
4413
+ };
4414
+ var lineRange = (declaration, sourceCode) => {
4415
+ const [start, end] = declaration.range;
4416
+ return [start, sourceCode.text[end] === "\n" ? end + 1 : end];
4417
+ };
4418
+ var isNameTaken = (sourceCode, program, name, except) => {
4419
+ const moduleScope = sourceCode.getScope(program).childScopes.find((scope) => {
4420
+ return scope.type === "module";
4421
+ });
4422
+ const variable = moduleScope?.set.get(name);
4423
+ return variable !== void 0 && variable.defs.some((def) => {
4424
+ return def.node !== except;
4425
+ });
4426
+ };
4427
+ var namespacesOf = (group) => {
4428
+ return group.flatMap((declaration) => {
4429
+ return declaration.specifiers.filter((specifier) => {
4430
+ return specifier.type === "ImportNamespaceSpecifier";
4431
+ });
4432
+ });
4433
+ };
4434
+ var reportWrongNamespaces = (ctx, wrong) => {
4435
+ const { context, sourceCode, source, name } = ctx;
4436
+ for (const specifier of wrong) {
4437
+ const variable = sourceCode.getDeclaredVariables(specifier)[0];
4438
+ const edits = ctx.canFix && wrong.length === 1 && variable ? rewriteBindings([{ variable, member: "" }], name) : null;
4439
+ context.report({
4440
+ node: specifier,
4441
+ messageId: "namespaceName",
4442
+ data: { source, name, actual: specifier.local.name },
4443
+ fix: edits === null ? null : (fixer) => {
4444
+ return [
4445
+ fixer.replaceText(specifier.local, name),
4446
+ ...edits.map((edit) => {
4447
+ return fixer.replaceTextRange(edit.range, edit.text);
4448
+ })
4449
+ ];
4450
+ }
4451
+ });
4452
+ }
4453
+ };
4454
+ var reportNamedImports = (ctx, offending, hasNamespace) => {
4455
+ const { context, sourceCode, source, name } = ctx;
4456
+ const bindings = offending.flatMap((declaration) => {
4457
+ return declaration.specifiers.filter(isValueBinding).map((specifier) => {
4458
+ return { variable: sourceCode.getDeclaredVariables(specifier)[0], member: importedName(specifier) };
4459
+ });
4460
+ });
4461
+ const edits = ctx.canFix ? rewriteBindings(bindings, name) : null;
4462
+ const host = hasNamespace ? void 0 : offending[0];
4463
+ const declarationFix = (fixer, declaration) => {
4464
+ const lines = [
4465
+ declaration === host ? `import * as ${name} from ${sourceCode.getText(declaration.source)}` : null,
4466
+ typeImportLine(declaration, sourceCode)
4467
+ ].filter((line) => {
4468
+ return line !== null;
4469
+ });
4470
+ return lines.length === 0 ? fixer.removeRange(lineRange(declaration, sourceCode)) : fixer.replaceText(declaration, lines.join("\n"));
4471
+ };
4472
+ for (const declaration of offending) {
4473
+ context.report({
4474
+ node: declaration,
4475
+ messageId: "namedImport",
4476
+ data: { source, name },
4477
+ fix: edits === null || declaration !== offending[0] ? null : (fixer) => {
4478
+ return [
4479
+ ...offending.map((each) => {
4480
+ return declarationFix(fixer, each);
4481
+ }),
4482
+ ...edits.map((edit) => {
4483
+ return fixer.replaceTextRange(edit.range, edit.text);
4484
+ })
4485
+ ];
4486
+ }
4487
+ });
4488
+ }
4489
+ };
4490
+ var checkGroup = (ctx, program) => {
4491
+ const namespaces = namespacesOf(ctx.group);
4492
+ const correct = namespaces.find((specifier) => {
4493
+ return specifier.local.name === ctx.name;
4494
+ });
4495
+ const wrong = namespaces.filter((specifier) => {
4496
+ return specifier !== correct;
4497
+ });
4498
+ const nameTaken = isNameTaken(ctx.sourceCode, program, ctx.name, correct);
4499
+ reportWrongNamespaces({ ...ctx, canFix: ctx.canFix && correct === void 0 && !nameTaken }, wrong);
4500
+ const offending = ctx.group.filter((declaration) => {
4501
+ return declaration.specifiers.some(isValueBinding);
4502
+ });
4503
+ if (offending.length > 0) {
4504
+ reportNamedImports({ ...ctx, canFix: ctx.canFix && wrong.length === 0 && !nameTaken }, offending, !!correct);
4505
+ }
4506
+ };
4507
+ var servicesNamespaceImport = {
4508
+ meta: {
4509
+ type: "suggestion",
4510
+ fixable: "code",
4511
+ docs: {
4512
+ description: "Import a feature's `services` module only as a namespace (`import * as service from '../services'`).",
4513
+ recommended: true,
4514
+ url: "https://github.com/ArthurSaenz/infra-kit/tree/main/apps/infra-kit/eslint-plugin#services-namespace-import"
4515
+ },
4516
+ schema: [
4517
+ {
4518
+ type: "object",
4519
+ properties: {
4520
+ name: {
4521
+ type: "string",
4522
+ minLength: 1,
4523
+ description: `The namespace binding (default '${DEFAULT_NAME}').`
4524
+ },
4525
+ paths: {
4526
+ type: "array",
4527
+ items: { type: "string" },
4528
+ description: "Optional glob patterns. When provided, the rule only runs for files whose path matches one of them."
4529
+ },
4530
+ ignore: {
4531
+ type: "array",
4532
+ items: { type: "string" },
4533
+ description: "Optional glob patterns. The rule is skipped for files whose path matches one of them, even if it also matches `paths`."
4534
+ }
4535
+ },
4536
+ additionalProperties: false
4537
+ }
4538
+ ],
4539
+ messages: {
4540
+ namedImport: "Import `{{source}}` as a namespace \u2014 `import * as {{name}} from '{{source}}'` \u2014 and read members as `{{name}}.<member>`.",
4541
+ namespaceName: "Name the `{{source}}` namespace `{{name}}`, not `{{actual}}`."
4542
+ }
4543
+ },
4544
+ create(context) {
4545
+ const options = context.options[0] ?? {};
4546
+ const filename = context.filename.split("\\").join("/");
4547
+ if (BARREL_FILE.test(filename) || !isInScope(filename, options.paths ?? [], options.ignore ?? []) || !isUiPackageFile(filename)) {
4548
+ return {};
4549
+ }
4550
+ const declarations = [];
4551
+ return {
4552
+ ImportDeclaration(node) {
4553
+ if (!isTypeOnly(node) && typeof node.source.value === "string" && SERVICES_SPECIFIER.test(node.source.value)) {
4554
+ declarations.push(node);
4555
+ }
4556
+ },
4557
+ "Program:exit": function(program) {
4558
+ const bySource = Map.groupBy(declarations, (declaration) => {
4559
+ return String(declaration.source.value);
4560
+ });
4561
+ for (const [source, group] of bySource) {
4562
+ checkGroup(
4563
+ {
4564
+ context,
4565
+ sourceCode: context.sourceCode,
4566
+ source,
4567
+ group,
4568
+ name: options.name ?? DEFAULT_NAME,
4569
+ // Two different services modules cannot share one namespace name.
4570
+ canFix: bySource.size === 1
4571
+ },
4572
+ program
4573
+ );
4574
+ }
4575
+ }
4576
+ };
4577
+ }
4578
+ };
4579
+
4351
4580
  // src/rules/test-location/test-location.ts
4352
4581
  var TEST_FILE3 = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
4353
4582
  var SPEC_SUFFIX = /\.spec(\.[cm]?[jt]sx?)$/;
@@ -4470,6 +4699,7 @@ var rules = {
4470
4699
  "atom-naming": atomNaming,
4471
4700
  "no-hooks-in-services": noHooksInServices,
4472
4701
  "feature-files": featureFiles,
4702
+ "services-namespace-import": servicesNamespaceImport,
4473
4703
  "component-name-matches-file": componentNameMatchesFile,
4474
4704
  "component-classname": componentClassname,
4475
4705
  "dumb-component-purity": dumbComponentPurity
@@ -4480,7 +4710,7 @@ var PLUGIN_NAME = "@wl";
4480
4710
  var plugin = {
4481
4711
  meta: {
4482
4712
  name: "@wl/eslint-plugin",
4483
- version: "0.44.0"
4713
+ version: "0.45.0"
4484
4714
  },
4485
4715
  rules,
4486
4716
  configs: {}
@@ -4605,6 +4835,7 @@ plugin.configs.recommended = [
4605
4835
  [`${PLUGIN_NAME}/atom-naming`]: "error",
4606
4836
  [`${PLUGIN_NAME}/no-hooks-in-services`]: "error",
4607
4837
  [`${PLUGIN_NAME}/feature-files`]: "error",
4838
+ [`${PLUGIN_NAME}/services-namespace-import`]: "error",
4608
4839
  [`${PLUGIN_NAME}/component-name-matches-file`]: "error",
4609
4840
  [`${PLUGIN_NAME}/component-classname`]: "error",
4610
4841
  [`${PLUGIN_NAME}/dumb-component-purity`]: "error"