@effect/language-service 0.44.1 → 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/README.md CHANGED
@@ -282,6 +282,27 @@ The skipLeadingPath array can contain a set of prefixes to remove from the subpa
282
282
 
283
283
  This pattern uses the package name + identifier. This usually works great if you have a flat structure, with one file per service/error.
284
284
 
285
+ ## Using Key Patterns in custom API definitions
286
+
287
+ You can enforce and take advantage of the deterministicKeys rule also in your own custom API that provide an `extends MyApi("identifier")`-like experience, so basically only in extends clause of class declarations.
288
+
289
+ To do so, first you need to enable `extendedKeyDetection: true` in plugin options to enable slower detection of this custom patterns.
290
+
291
+ And then you'll need to add a JSDoc `/** @effect-identifier */` to the parameter where you expect to receive string identifier.
292
+
293
+ Let's say for example that you want to provide a Repository() API that is basically the same of Context.Tag, but prefixes the key identifier with 'Repository/'; the definition of the Resource API would be something like this:
294
+
295
+ ```ts
296
+ export function Repository(/** @effect-identifier */ identifier: string) {
297
+ return Context.Tag("Repository/" + identifier)
298
+ }
299
+ ```
300
+
301
+ and will be used as follows:
302
+ ```ts
303
+ export class UserRepo extends Repository("Hello")<UserRepo, { /** ... */ }>() {}
304
+ ```
305
+
285
306
  ## Known gotchas
286
307
 
287
308
  ### Svelte VSCode extension and SvelteKit
@@ -1190,14 +1190,19 @@ var defaults = {
1190
1190
  target: "service",
1191
1191
  pattern: "default",
1192
1192
  skipLeadingPath: ["src/"]
1193
- }]
1193
+ }, {
1194
+ target: "custom",
1195
+ pattern: "default",
1196
+ skipLeadingPath: ["src/"]
1197
+ }],
1198
+ extendedKeyDetection: false
1194
1199
  };
1195
1200
  function parseKeyPatterns(patterns) {
1196
1201
  const result = [];
1197
1202
  for (const entry of patterns) {
1198
1203
  if (!isObject(entry)) continue;
1199
1204
  result.push({
1200
- target: hasProperty(entry, "target") && isString(entry.target) && ["service", "error"].includes(entry.target.toLowerCase()) ? entry.target.toLowerCase() : "service",
1205
+ target: hasProperty(entry, "target") && isString(entry.target) && ["service", "error", "custom"].includes(entry.target.toLowerCase()) ? entry.target.toLowerCase() : "service",
1201
1206
  pattern: hasProperty(entry, "pattern") && isString(entry.pattern) && ["package-identifier", "default"].includes(entry.pattern.toLowerCase()) ? entry.pattern.toLowerCase() : "default",
1202
1207
  skipLeadingPath: hasProperty(entry, "skipLeadingPath") && isArray(entry.skipLeadingPath) && entry.skipLeadingPath.every(isString) ? entry.skipLeadingPath : ["src/"]
1203
1208
  });
@@ -1223,7 +1228,8 @@ function parse(config) {
1223
1228
  topLevelNamedReexports: isObject(config) && hasProperty(config, "topLevelNamedReexports") && isString(config.topLevelNamedReexports) && ["ignore", "follow"].includes(config.topLevelNamedReexports.toLowerCase()) ? config.topLevelNamedReexports.toLowerCase() : defaults.topLevelNamedReexports,
1224
1229
  renames: isObject(config) && hasProperty(config, "renames") && isBoolean(config.renames) ? config.renames : defaults.renames,
1225
1230
  noExternal: isObject(config) && hasProperty(config, "noExternal") && isBoolean(config.noExternal) ? config.noExternal : defaults.noExternal,
1226
- keyPatterns: isObject(config) && hasProperty(config, "keyPatterns") && isArray(config.keyPatterns) ? parseKeyPatterns(config.keyPatterns) : defaults.keyPatterns
1231
+ keyPatterns: isObject(config) && hasProperty(config, "keyPatterns") && isArray(config.keyPatterns) ? parseKeyPatterns(config.keyPatterns) : defaults.keyPatterns,
1232
+ extendedKeyDetection: isObject(config) && hasProperty(config, "extendedKeyDetection") && isBoolean(config.extendedKeyDetection) ? config.extendedKeyDetection : defaults.extendedKeyDetection
1227
1233
  };
1228
1234
  }
1229
1235
 
@@ -3551,6 +3557,62 @@ var deterministicKeys = createDiagnostic({
3551
3557
  apply: fn("deterministicKeys.apply")(function* (sourceFile, report) {
3552
3558
  const ts = yield* service(TypeScriptApi);
3553
3559
  const typeParser = yield* service(TypeParser);
3560
+ const typeChecker = yield* service(TypeCheckerApi);
3561
+ const typeScriptUtils = yield* service(TypeScriptUtils);
3562
+ const options = yield* service(LanguageServicePluginOptions);
3563
+ const parseExtendsCustom = cachedBy(
3564
+ fn("parseExtendsCustom")(function* (classDeclaration) {
3565
+ if (!options.extendedKeyDetection) {
3566
+ return yield* typeParserIssue("Extended key detection is disabled", void 0, classDeclaration);
3567
+ }
3568
+ if (!classDeclaration.name) {
3569
+ return yield* typeParserIssue("Class has no name", void 0, classDeclaration);
3570
+ }
3571
+ if (!ts.isIdentifier(classDeclaration.name)) {
3572
+ return yield* typeParserIssue("Class name is not an identifier", void 0, classDeclaration);
3573
+ }
3574
+ const heritageClauses = classDeclaration.heritageClauses;
3575
+ if (!heritageClauses) {
3576
+ return yield* typeParserIssue("Class has no heritage clauses", void 0, classDeclaration);
3577
+ }
3578
+ const nodeToVisit2 = [...classDeclaration.heritageClauses];
3579
+ const appendNodeToVisit2 = (node) => {
3580
+ nodeToVisit2.push(node);
3581
+ return void 0;
3582
+ };
3583
+ while (nodeToVisit2.length > 0) {
3584
+ const node = nodeToVisit2.shift();
3585
+ if (ts.isCallExpression(node)) {
3586
+ for (let i = 0; i < node.arguments.length; i++) {
3587
+ const arg = node.arguments[i];
3588
+ if (!ts.isStringLiteral(arg)) continue;
3589
+ const resolvedSignature = typeChecker.getResolvedSignature(node);
3590
+ if (resolvedSignature) {
3591
+ const parameter = resolvedSignature.parameters[i];
3592
+ if (!parameter) continue;
3593
+ if (parameter.declarations) {
3594
+ for (const declaration of parameter.declarations) {
3595
+ const parameterSourceFile = typeScriptUtils.getSourceFileOfNode(declaration);
3596
+ const paramText = parameterSourceFile.text.substring(declaration.pos, declaration.end);
3597
+ if (paramText.toLowerCase().includes("@effect-identifier")) {
3598
+ return { className: classDeclaration.name, keyStringLiteral: arg, target: "custom" };
3599
+ }
3600
+ }
3601
+ }
3602
+ }
3603
+ }
3604
+ }
3605
+ ts.forEachChild(node, appendNodeToVisit2);
3606
+ }
3607
+ return yield* typeParserIssue(
3608
+ "Class does not extend any custom pattern",
3609
+ void 0,
3610
+ classDeclaration
3611
+ );
3612
+ }),
3613
+ "deterministicKeys.parseExtendsCustom",
3614
+ (classDeclaration) => classDeclaration
3615
+ );
3554
3616
  const nodeToVisit = [];
3555
3617
  const appendNodeToVisit = (node) => {
3556
3618
  nodeToVisit.push(node);
@@ -3574,12 +3636,13 @@ var deterministicKeys = createDiagnostic({
3574
3636
  map4(({ className, keyStringLiteral }) => ({ keyStringLiteral, className, target: "error" }))
3575
3637
  )
3576
3638
  ),
3639
+ orElse2(() => parseExtendsCustom(node)),
3577
3640
  orElse2(() => void_)
3578
3641
  );
3579
3642
  if (result && result.keyStringLiteral) {
3580
- const { className, keyStringLiteral } = result;
3643
+ const { className, keyStringLiteral, target } = result;
3581
3644
  const classNameText = ts.idText(className);
3582
- const expectedKey = yield* createString(sourceFile, classNameText, result.target);
3645
+ const expectedKey = yield* createString(sourceFile, classNameText, target);
3583
3646
  if (!expectedKey) continue;
3584
3647
  const actualIdentifier = keyStringLiteral.text;
3585
3648
  if (actualIdentifier !== expectedKey) {