@intentius/chant-lexicon-gitlab 0.46.0 → 0.49.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/integrity.json +6 -5
- package/dist/lint/post-synth/wgl019.d.ts.map +1 -1
- package/dist/lint/rules/import-source.d.ts +28 -0
- package/dist/lint/rules/import-source.d.ts.map +1 -0
- package/dist/lint/rules/missing-script.d.ts.map +1 -1
- package/dist/lint/rules/missing-stage.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/rules/import-source.ts +58 -0
- package/dist/rules/missing-script.ts +13 -0
- package/dist/rules/missing-stage.ts +8 -0
- package/dist/rules/wgl019.ts +6 -1
- package/package.json +3 -3
- package/src/lint/post-synth/wgl019.test.ts +8 -0
- package/src/lint/post-synth/wgl019.ts +6 -1
- package/src/lint/rules/import-source.ts +58 -0
- package/src/lint/rules/missing-script.ts +13 -0
- package/src/lint/rules/missing-stage.ts +8 -0
- package/src/lint/rules/rules.test.ts +60 -0
package/dist/integrity.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "6e5bfe31912b7af3fa1bd5241c2a22ae87b5e95afbe871d0736fd8d96cd3cd69",
|
|
5
5
|
"meta.json": "931fc3246a55645b1493080bbeb160e5d205e42349e8bdca96ca243adb5f0da3",
|
|
6
6
|
"types/index.d.ts": "5cd2e99f135a929b72bdd822d00d780d39b1cd407ba0cfb3d511c7ac9d667b58",
|
|
7
7
|
"rules/artifact-no-expiry.ts": "3f3cabf9792cbf8207e53a25f506715466b19ec25e9c3b4d0d77fed6b2eb4542",
|
|
8
8
|
"rules/deprecated-only-except.ts": "2341b95c0aaf3013c375fb404969858e23451e3430f3d1a63b128fcb21b90156",
|
|
9
|
-
"rules/
|
|
10
|
-
"rules/missing-
|
|
9
|
+
"rules/import-source.ts": "b5b2e79ef4c18a21711a380dce60e8bbab840223eda568b18039879d3a0a549f",
|
|
10
|
+
"rules/missing-script.ts": "c9efd57e2d5cc0a022c6e7858f7ffa98d2fbb055e6ea915d2d54cf2b2d130e47",
|
|
11
|
+
"rules/missing-stage.ts": "b0f4b7e0b70e496ddc9d239d78a26410da4c18fcf9c518813523bb2459ffa5f7",
|
|
11
12
|
"rules/wgl010.ts": "e9a14d5f9e4c49b1524ba5ee3746be739603125ec3c2dc4eb4b7c0bfc32581b0",
|
|
12
13
|
"rules/wgl011.ts": "2fbb164669204ac8c3662b9c1ae156b59589913e59dd596fb38f289a0f218aaf",
|
|
13
14
|
"rules/wgl012.ts": "ec4bd3fe93d30824c74223bb4e5929e493a06561a7bd3265b68dc1ba5d734da4",
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
"rules/wgl016.ts": "d8bf9a371b22f37a767f92e3361d5f8e064e6dd6ba879ff1a5fc90b4b09fd304",
|
|
18
19
|
"rules/wgl017.ts": "2d212de62eee4f7b086b49c92975b09d988c1ff05c2fb20cffccdc500a14e668",
|
|
19
20
|
"rules/wgl018.ts": "dba1e1357becb1bbea9b435d9ce88b12138a53d8fc934483bc0ec8f22e4b18f5",
|
|
20
|
-
"rules/wgl019.ts": "
|
|
21
|
+
"rules/wgl019.ts": "01d703b2734385c3a9c6a132e1988f07d24636aa5e8fc22d8e06cc7b4aae5d97",
|
|
21
22
|
"rules/wgl020.ts": "81a84b49308f36ab2b01342bd0e262b59b2a8395d233760ac52a679c2a4ae0e6",
|
|
22
23
|
"rules/wgl021.ts": "59a75e0b0123779bcd4c7f66eb068c4302860df8333a4bc7b41c68e3983505da",
|
|
23
24
|
"rules/wgl022.ts": "96b87f8f410f5ffa6358823f4c8cb08a59166a7575166ede24e8c41b50f3be65",
|
|
@@ -52,5 +53,5 @@
|
|
|
52
53
|
"skills/chant-gitlab-migrate.md": "4853d04980560b379e5b0f7267dcb0a1e5d5879cb9e9931681ce76b2a5fe69d8",
|
|
53
54
|
"skills/chant-gitlab-patterns.md": "6d9a44e9e8de4c3820be9d65381b0d2ede4cb3626aaaf0011e21838a9fbf556e"
|
|
54
55
|
},
|
|
55
|
-
"composite": "
|
|
56
|
+
"composite": "9f91a75e02d3e027b840d19762da56a8155b6c95227917df289e27fe46d48f8b"
|
|
56
57
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wgl019.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/wgl019.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,kCAAkC,CAAC;AAK9G,eAAO,MAAM,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"wgl019.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/wgl019.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,kCAAkC,CAAC;AAK9G,eAAO,MAAM,MAAM,EAAE,cAoCpB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for gitlab lint rules that match a bare TypeScript
|
|
3
|
+
* identifier (`Job`, ...) and need to know whether it actually came from
|
|
4
|
+
* this lexicon before flagging it (chant #1544 — cross-lexicon rule bleed:
|
|
5
|
+
* `new Job(...)` matches ANY lexicon's `Job` class by name alone, so a
|
|
6
|
+
* multi-lexicon project got gitlab-only rules applied to github/forgejo
|
|
7
|
+
* jobs that were never missing anything).
|
|
8
|
+
*/
|
|
9
|
+
import * as ts from "typescript";
|
|
10
|
+
/**
|
|
11
|
+
* The module specifier a top-level import bound `name` to, whether via a
|
|
12
|
+
* named import (`import { Job } from "..."`, matched on its local —
|
|
13
|
+
* possibly aliased — binding) or a namespace import (`import * as gl from
|
|
14
|
+
* "..."`, matched on the namespace's own name). Undefined when `name` isn't
|
|
15
|
+
* bound by any top-level import in this file (no import at all, a
|
|
16
|
+
* re-export chain, a dynamic `require`, …) — the caller should treat that
|
|
17
|
+
* as "can't tell", not "not gitlab", to stay conservative for the common
|
|
18
|
+
* single-lexicon-project case (and every pre-#1544 unit test fixture, which
|
|
19
|
+
* has no import statements at all).
|
|
20
|
+
*/
|
|
21
|
+
export declare function importSourceFor(sourceFile: ts.SourceFile, name: string): string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* True when `expression` (a `new`-expression callee) resolves — via a
|
|
24
|
+
* traced import — to a lexicon OTHER than gitlab. False for an unresolved
|
|
25
|
+
* import (conservative: still checked) or one that does resolve to gitlab.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isJobFromAnotherLexicon(sourceFile: ts.SourceFile, expression: ts.LeftHandSideExpression): boolean;
|
|
28
|
+
//# sourceMappingURL=import-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-source.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/import-source.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAiB3F;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE,CAAC,sBAAsB,GAAG,OAAO,CAWjH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"missing-script.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/missing-script.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAA+B,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"missing-script.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/missing-script.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAA+B,MAAM,4BAA4B,CAAC;AAMxF,eAAO,MAAM,iBAAiB,EAAE,QAgE/B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"missing-stage.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/missing-stage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAA+B,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"missing-stage.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/missing-stage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAA+B,MAAM,4BAA4B,CAAC;AAIxF,eAAO,MAAM,gBAAgB,EAAE,QA0D9B,CAAC"}
|
package/dist/manifest.json
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for gitlab lint rules that match a bare TypeScript
|
|
3
|
+
* identifier (`Job`, ...) and need to know whether it actually came from
|
|
4
|
+
* this lexicon before flagging it (chant #1544 — cross-lexicon rule bleed:
|
|
5
|
+
* `new Job(...)` matches ANY lexicon's `Job` class by name alone, so a
|
|
6
|
+
* multi-lexicon project got gitlab-only rules applied to github/forgejo
|
|
7
|
+
* jobs that were never missing anything).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as ts from "typescript";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The module specifier a top-level import bound `name` to, whether via a
|
|
14
|
+
* named import (`import { Job } from "..."`, matched on its local —
|
|
15
|
+
* possibly aliased — binding) or a namespace import (`import * as gl from
|
|
16
|
+
* "..."`, matched on the namespace's own name). Undefined when `name` isn't
|
|
17
|
+
* bound by any top-level import in this file (no import at all, a
|
|
18
|
+
* re-export chain, a dynamic `require`, …) — the caller should treat that
|
|
19
|
+
* as "can't tell", not "not gitlab", to stay conservative for the common
|
|
20
|
+
* single-lexicon-project case (and every pre-#1544 unit test fixture, which
|
|
21
|
+
* has no import statements at all).
|
|
22
|
+
*/
|
|
23
|
+
export function importSourceFor(sourceFile: ts.SourceFile, name: string): string | undefined {
|
|
24
|
+
for (const stmt of sourceFile.statements) {
|
|
25
|
+
if (!ts.isImportDeclaration(stmt)) continue;
|
|
26
|
+
const moduleSpecifier = stmt.moduleSpecifier;
|
|
27
|
+
if (!ts.isStringLiteral(moduleSpecifier)) continue;
|
|
28
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
29
|
+
if (!namedBindings) continue;
|
|
30
|
+
|
|
31
|
+
if (ts.isNamespaceImport(namedBindings)) {
|
|
32
|
+
if (namedBindings.name.text === name) return moduleSpecifier.text;
|
|
33
|
+
} else if (ts.isNamedImports(namedBindings)) {
|
|
34
|
+
for (const element of namedBindings.elements) {
|
|
35
|
+
if (element.name.text === name) return moduleSpecifier.text;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* True when `expression` (a `new`-expression callee) resolves — via a
|
|
44
|
+
* traced import — to a lexicon OTHER than gitlab. False for an unresolved
|
|
45
|
+
* import (conservative: still checked) or one that does resolve to gitlab.
|
|
46
|
+
*/
|
|
47
|
+
export function isJobFromAnotherLexicon(sourceFile: ts.SourceFile, expression: ts.LeftHandSideExpression): boolean {
|
|
48
|
+
let bindingName: string | undefined;
|
|
49
|
+
if (ts.isIdentifier(expression)) {
|
|
50
|
+
bindingName = expression.text;
|
|
51
|
+
} else if (ts.isPropertyAccessExpression(expression)) {
|
|
52
|
+
bindingName = ts.isIdentifier(expression.expression) ? expression.expression.text : undefined;
|
|
53
|
+
}
|
|
54
|
+
if (!bindingName) return false;
|
|
55
|
+
|
|
56
|
+
const source = importSourceFor(sourceFile, bindingName);
|
|
57
|
+
return source !== undefined && !source.includes("chant-lexicon-gitlab");
|
|
58
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
9
9
|
import * as ts from "typescript";
|
|
10
|
+
import { isJobFromAnotherLexicon } from "./import-source";
|
|
10
11
|
|
|
11
12
|
const VALID_EXECUTION_PROPS = new Set(["script", "trigger", "run"]);
|
|
12
13
|
|
|
@@ -31,6 +32,18 @@ export const missingScriptRule: LintRule = {
|
|
|
31
32
|
isJob = true;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
// chant #1544 — cross-lexicon rule bleed: `new Job(...)` matches
|
|
36
|
+
// ANY lexicon's `Job` class by bare name alone (github's, forgejo's,
|
|
37
|
+
// ...), so a multi-lexicon project got WGL002 (gitlab-only) applied
|
|
38
|
+
// to github/forgejo jobs that were never missing anything — they
|
|
39
|
+
// just don't use `script`/`trigger`/`run`. Skip when the import
|
|
40
|
+
// resolves to a lexicon other than gitlab; an unresolved import
|
|
41
|
+
// (no `import` statement — most unit-test fixtures, a re-export) is
|
|
42
|
+
// "can't tell", so it keeps the previous, conservative behavior.
|
|
43
|
+
if (isJob && isJobFromAnotherLexicon(sourceFile, expression)) {
|
|
44
|
+
isJob = false;
|
|
45
|
+
}
|
|
46
|
+
|
|
34
47
|
if (isJob && node.arguments && node.arguments.length > 0) {
|
|
35
48
|
const props = node.arguments[0];
|
|
36
49
|
if (ts.isObjectLiteralExpression(props)) {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
9
9
|
import * as ts from "typescript";
|
|
10
|
+
import { isJobFromAnotherLexicon } from "./import-source";
|
|
10
11
|
|
|
11
12
|
export const missingStageRule: LintRule = {
|
|
12
13
|
id: "WGL003",
|
|
@@ -28,6 +29,13 @@ export const missingStageRule: LintRule = {
|
|
|
28
29
|
isJob = true;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
// chant #1544 — same cross-lexicon rule bleed as WGL002 (see
|
|
33
|
+
// missing-script.ts): a github/forgejo `Job` must not be judged
|
|
34
|
+
// against gitlab's `stage` convention.
|
|
35
|
+
if (isJob && isJobFromAnotherLexicon(sourceFile, expression)) {
|
|
36
|
+
isJob = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
if (isJob && node.arguments && node.arguments.length > 0) {
|
|
32
40
|
const props = node.arguments[0];
|
|
33
41
|
if (ts.isObjectLiteralExpression(props)) {
|
package/dist/rules/wgl019.ts
CHANGED
|
@@ -28,7 +28,12 @@ export const wgl019: PostSynthCheck = {
|
|
|
28
28
|
const stage = props.stage as string | undefined;
|
|
29
29
|
if (!stage || !DEPLOY_STAGES.has(stage.toLowerCase())) continue;
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
// chant #1544 — `!props.retry` treated an explicit `retry: 0` the same
|
|
32
|
+
// as no `retry` at all, warning on a job that stated its policy
|
|
33
|
+
// ("retrying a deploy without a human is deliberate") as if it hadn't.
|
|
34
|
+
// `retry: 0` is falsy but present — only an actually-absent key means
|
|
35
|
+
// "no retry strategy".
|
|
36
|
+
if (props.retry === undefined) {
|
|
32
37
|
diagnostics.push({
|
|
33
38
|
checkId: "WGL019",
|
|
34
39
|
severity: "info",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-gitlab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "GitLab CI lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"typescript": "^5.9.3"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@intentius/chant": "^0.
|
|
71
|
-
"@intentius/chant-lexicon-github": "^0.
|
|
70
|
+
"@intentius/chant": "^0.49.0",
|
|
71
|
+
"@intentius/chant-lexicon-github": "^0.49.0",
|
|
72
72
|
"typescript": "^5.9.3"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|
|
@@ -53,6 +53,14 @@ describe("WGL019: Missing Retry on Deploy Jobs", () => {
|
|
|
53
53
|
expect(diags).toHaveLength(0);
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
test("does not flag deploy job with explicit retry: 0 (#1544)", () => {
|
|
57
|
+
const entities = new Map<string, Declarable>([
|
|
58
|
+
["deployApp", new MockJob({ script: ["deploy.sh"], stage: "deploy", retry: 0 })],
|
|
59
|
+
]);
|
|
60
|
+
const diags = wgl019.check(makeCtx(entities));
|
|
61
|
+
expect(diags).toHaveLength(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
56
64
|
test("does not flag non-deploy job without retry", () => {
|
|
57
65
|
const entities = new Map<string, Declarable>([
|
|
58
66
|
["testJob", new MockJob({ script: ["npm test"], stage: "test" })],
|
|
@@ -28,7 +28,12 @@ export const wgl019: PostSynthCheck = {
|
|
|
28
28
|
const stage = props.stage as string | undefined;
|
|
29
29
|
if (!stage || !DEPLOY_STAGES.has(stage.toLowerCase())) continue;
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
// chant #1544 — `!props.retry` treated an explicit `retry: 0` the same
|
|
32
|
+
// as no `retry` at all, warning on a job that stated its policy
|
|
33
|
+
// ("retrying a deploy without a human is deliberate") as if it hadn't.
|
|
34
|
+
// `retry: 0` is falsy but present — only an actually-absent key means
|
|
35
|
+
// "no retry strategy".
|
|
36
|
+
if (props.retry === undefined) {
|
|
32
37
|
diagnostics.push({
|
|
33
38
|
checkId: "WGL019",
|
|
34
39
|
severity: "info",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for gitlab lint rules that match a bare TypeScript
|
|
3
|
+
* identifier (`Job`, ...) and need to know whether it actually came from
|
|
4
|
+
* this lexicon before flagging it (chant #1544 — cross-lexicon rule bleed:
|
|
5
|
+
* `new Job(...)` matches ANY lexicon's `Job` class by name alone, so a
|
|
6
|
+
* multi-lexicon project got gitlab-only rules applied to github/forgejo
|
|
7
|
+
* jobs that were never missing anything).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as ts from "typescript";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The module specifier a top-level import bound `name` to, whether via a
|
|
14
|
+
* named import (`import { Job } from "..."`, matched on its local —
|
|
15
|
+
* possibly aliased — binding) or a namespace import (`import * as gl from
|
|
16
|
+
* "..."`, matched on the namespace's own name). Undefined when `name` isn't
|
|
17
|
+
* bound by any top-level import in this file (no import at all, a
|
|
18
|
+
* re-export chain, a dynamic `require`, …) — the caller should treat that
|
|
19
|
+
* as "can't tell", not "not gitlab", to stay conservative for the common
|
|
20
|
+
* single-lexicon-project case (and every pre-#1544 unit test fixture, which
|
|
21
|
+
* has no import statements at all).
|
|
22
|
+
*/
|
|
23
|
+
export function importSourceFor(sourceFile: ts.SourceFile, name: string): string | undefined {
|
|
24
|
+
for (const stmt of sourceFile.statements) {
|
|
25
|
+
if (!ts.isImportDeclaration(stmt)) continue;
|
|
26
|
+
const moduleSpecifier = stmt.moduleSpecifier;
|
|
27
|
+
if (!ts.isStringLiteral(moduleSpecifier)) continue;
|
|
28
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
29
|
+
if (!namedBindings) continue;
|
|
30
|
+
|
|
31
|
+
if (ts.isNamespaceImport(namedBindings)) {
|
|
32
|
+
if (namedBindings.name.text === name) return moduleSpecifier.text;
|
|
33
|
+
} else if (ts.isNamedImports(namedBindings)) {
|
|
34
|
+
for (const element of namedBindings.elements) {
|
|
35
|
+
if (element.name.text === name) return moduleSpecifier.text;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* True when `expression` (a `new`-expression callee) resolves — via a
|
|
44
|
+
* traced import — to a lexicon OTHER than gitlab. False for an unresolved
|
|
45
|
+
* import (conservative: still checked) or one that does resolve to gitlab.
|
|
46
|
+
*/
|
|
47
|
+
export function isJobFromAnotherLexicon(sourceFile: ts.SourceFile, expression: ts.LeftHandSideExpression): boolean {
|
|
48
|
+
let bindingName: string | undefined;
|
|
49
|
+
if (ts.isIdentifier(expression)) {
|
|
50
|
+
bindingName = expression.text;
|
|
51
|
+
} else if (ts.isPropertyAccessExpression(expression)) {
|
|
52
|
+
bindingName = ts.isIdentifier(expression.expression) ? expression.expression.text : undefined;
|
|
53
|
+
}
|
|
54
|
+
if (!bindingName) return false;
|
|
55
|
+
|
|
56
|
+
const source = importSourceFor(sourceFile, bindingName);
|
|
57
|
+
return source !== undefined && !source.includes("chant-lexicon-gitlab");
|
|
58
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
9
9
|
import * as ts from "typescript";
|
|
10
|
+
import { isJobFromAnotherLexicon } from "./import-source";
|
|
10
11
|
|
|
11
12
|
const VALID_EXECUTION_PROPS = new Set(["script", "trigger", "run"]);
|
|
12
13
|
|
|
@@ -31,6 +32,18 @@ export const missingScriptRule: LintRule = {
|
|
|
31
32
|
isJob = true;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
// chant #1544 — cross-lexicon rule bleed: `new Job(...)` matches
|
|
36
|
+
// ANY lexicon's `Job` class by bare name alone (github's, forgejo's,
|
|
37
|
+
// ...), so a multi-lexicon project got WGL002 (gitlab-only) applied
|
|
38
|
+
// to github/forgejo jobs that were never missing anything — they
|
|
39
|
+
// just don't use `script`/`trigger`/`run`. Skip when the import
|
|
40
|
+
// resolves to a lexicon other than gitlab; an unresolved import
|
|
41
|
+
// (no `import` statement — most unit-test fixtures, a re-export) is
|
|
42
|
+
// "can't tell", so it keeps the previous, conservative behavior.
|
|
43
|
+
if (isJob && isJobFromAnotherLexicon(sourceFile, expression)) {
|
|
44
|
+
isJob = false;
|
|
45
|
+
}
|
|
46
|
+
|
|
34
47
|
if (isJob && node.arguments && node.arguments.length > 0) {
|
|
35
48
|
const props = node.arguments[0];
|
|
36
49
|
if (ts.isObjectLiteralExpression(props)) {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
9
9
|
import * as ts from "typescript";
|
|
10
|
+
import { isJobFromAnotherLexicon } from "./import-source";
|
|
10
11
|
|
|
11
12
|
export const missingStageRule: LintRule = {
|
|
12
13
|
id: "WGL003",
|
|
@@ -28,6 +29,13 @@ export const missingStageRule: LintRule = {
|
|
|
28
29
|
isJob = true;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
// chant #1544 — same cross-lexicon rule bleed as WGL002 (see
|
|
33
|
+
// missing-script.ts): a github/forgejo `Job` must not be judged
|
|
34
|
+
// against gitlab's `stage` convention.
|
|
35
|
+
if (isJob && isJobFromAnotherLexicon(sourceFile, expression)) {
|
|
36
|
+
isJob = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
if (isJob && node.arguments && node.arguments.length > 0) {
|
|
32
40
|
const props = node.arguments[0];
|
|
33
41
|
if (ts.isObjectLiteralExpression(props)) {
|
|
@@ -88,6 +88,49 @@ describe("WGL002: missing-script", () => {
|
|
|
88
88
|
const diags = missingScriptRule.check(ctx);
|
|
89
89
|
expect(diags).toHaveLength(0);
|
|
90
90
|
});
|
|
91
|
+
|
|
92
|
+
// chant #1544 — cross-lexicon rule bleed: a github/forgejo `Job` imported
|
|
93
|
+
// from its own lexicon package must not be judged by gitlab's `script`/
|
|
94
|
+
// `trigger`/`run` requirement.
|
|
95
|
+
test("does not flag a Job imported from the github lexicon", () => {
|
|
96
|
+
const ctx = createContext(
|
|
97
|
+
`import { Job } from "@intentius/chant-lexicon-github";\nconst j = new Job({ "runs-on": "ubuntu-latest", steps: [] });`,
|
|
98
|
+
);
|
|
99
|
+
const diags = missingScriptRule.check(ctx);
|
|
100
|
+
expect(diags).toHaveLength(0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("does not flag a Job imported from the forgejo lexicon", () => {
|
|
104
|
+
const ctx = createContext(
|
|
105
|
+
`import { Job } from "@intentius/chant-lexicon-forgejo";\nconst j = new Job({ "runs-on": "ubuntu-latest", steps: [] });`,
|
|
106
|
+
);
|
|
107
|
+
const diags = missingScriptRule.check(ctx);
|
|
108
|
+
expect(diags).toHaveLength(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("still flags a Job imported from the gitlab lexicon", () => {
|
|
112
|
+
const ctx = createContext(
|
|
113
|
+
`import { Job } from "@intentius/chant-lexicon-gitlab";\nconst j = new Job({ stage: "test" });`,
|
|
114
|
+
);
|
|
115
|
+
const diags = missingScriptRule.check(ctx);
|
|
116
|
+
expect(diags).toHaveLength(1);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("still flags a namespace-imported gl.Job() from the gitlab lexicon", () => {
|
|
120
|
+
const ctx = createContext(
|
|
121
|
+
`import * as gl from "@intentius/chant-lexicon-gitlab";\nconst j = new gl.Job({ stage: "build" });`,
|
|
122
|
+
);
|
|
123
|
+
const diags = missingScriptRule.check(ctx);
|
|
124
|
+
expect(diags).toHaveLength(1);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("does not flag a namespace-imported Job from another lexicon", () => {
|
|
128
|
+
const ctx = createContext(
|
|
129
|
+
`import * as gh from "@intentius/chant-lexicon-github";\nconst j = new gh.Job({ "runs-on": "ubuntu-latest", steps: [] });`,
|
|
130
|
+
);
|
|
131
|
+
const diags = missingScriptRule.check(ctx);
|
|
132
|
+
expect(diags).toHaveLength(0);
|
|
133
|
+
});
|
|
91
134
|
});
|
|
92
135
|
|
|
93
136
|
// ── WGL003: missing stage ───────────────────────────────────────────
|
|
@@ -107,6 +150,23 @@ describe("WGL003: missing-stage", () => {
|
|
|
107
150
|
const diags = missingStageRule.check(ctx);
|
|
108
151
|
expect(diags).toHaveLength(0);
|
|
109
152
|
});
|
|
153
|
+
|
|
154
|
+
// chant #1544 — same cross-lexicon rule bleed as WGL002.
|
|
155
|
+
test("does not flag a Job imported from the github lexicon", () => {
|
|
156
|
+
const ctx = createContext(
|
|
157
|
+
`import { Job } from "@intentius/chant-lexicon-github";\nconst j = new Job({ "runs-on": "ubuntu-latest", steps: [] });`,
|
|
158
|
+
);
|
|
159
|
+
const diags = missingStageRule.check(ctx);
|
|
160
|
+
expect(diags).toHaveLength(0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("still flags a Job imported from the gitlab lexicon", () => {
|
|
164
|
+
const ctx = createContext(
|
|
165
|
+
`import { Job } from "@intentius/chant-lexicon-gitlab";\nconst j = new Job({ script: ["test"] });`,
|
|
166
|
+
);
|
|
167
|
+
const diags = missingStageRule.check(ctx);
|
|
168
|
+
expect(diags).toHaveLength(1);
|
|
169
|
+
});
|
|
110
170
|
});
|
|
111
171
|
|
|
112
172
|
// ── WGL004: artifact no expiry ──────────────────────────────────────
|