@northguild/gmt-oxlint 1.1.5
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/LICENSE +21 -0
- package/README.md +142 -0
- package/config/recommended.d.ts +12 -0
- package/config/recommended.json +10 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +213 -0
- package/package.json +76 -0
- package/skills/ci-enforcement/SKILL.md +41 -0
- package/skills/first-time-setup/SKILL.md +46 -0
- package/skills/migration-refactor/SKILL.md +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NorthGuild
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# @northguild/gmt-oxlint
|
|
2
|
+
|
|
3
|
+
Shared [Oxlint](https://oxc.rs/docs/guide/usage/linter) JS plugin for `@northguild/gmt` projects.
|
|
4
|
+
|
|
5
|
+
It enforces the same Date bans used by `@northguild/gmt-eslint` and `@northguild/gmt-biome`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### npm
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install --save-dev @northguild/gmt-oxlint oxlint
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### yarn
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
yarn add --dev @northguild/gmt-oxlint oxlint
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### pnpm
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
pnpm add --save-dev @northguild/gmt-oxlint oxlint
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### bun
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
bun add --save-dev @northguild/gmt-oxlint oxlint
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Quick start (recommended for best DX)
|
|
36
|
+
|
|
37
|
+
In `oxlint.config.ts`:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { defineConfig } from "oxlint";
|
|
41
|
+
import { recommendedConfig } from "@northguild/gmt-oxlint";
|
|
42
|
+
|
|
43
|
+
export default defineConfig(recommendedConfig);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
If you prefer a custom alias for shorter rule names:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { defineConfig } from "oxlint";
|
|
50
|
+
import { recommendedRules } from "@northguild/gmt-oxlint";
|
|
51
|
+
|
|
52
|
+
export default defineConfig({
|
|
53
|
+
jsPlugins: [{ name: "gmt", specifier: "@northguild/gmt-oxlint" }],
|
|
54
|
+
rules: {
|
|
55
|
+
// Copy rules under your alias if you want "gmt/..." rule IDs.
|
|
56
|
+
"gmt/no-date-global": "error",
|
|
57
|
+
"gmt/no-new-date": "error",
|
|
58
|
+
"gmt/no-date-now": "error",
|
|
59
|
+
"gmt/no-date-parse": "error",
|
|
60
|
+
"gmt/no-date-utc": "error",
|
|
61
|
+
"gmt/no-date-getTimezoneOffset": "error",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### JSON config quick start
|
|
67
|
+
|
|
68
|
+
In your `.oxlintrc.json`:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
73
|
+
"jsPlugins": ["@northguild/gmt-oxlint"],
|
|
74
|
+
"extends": ["./node_modules/@northguild/gmt-oxlint/config/recommended.json"]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Oxlint currently resolves `extends` as file paths. Named shared config specifiers
|
|
79
|
+
such as `@northguild/gmt-oxlint/recommended` are not currently supported.
|
|
80
|
+
|
|
81
|
+
### Manual configuration
|
|
82
|
+
|
|
83
|
+
Or explicitly configure each rule. You can use either a string or an array form:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
88
|
+
"jsPlugins": ["@northguild/gmt-oxlint"],
|
|
89
|
+
"rules": {
|
|
90
|
+
"@northguild/gmt-oxlint/no-date-global": "error",
|
|
91
|
+
"@northguild/gmt-oxlint/no-new-date": "error",
|
|
92
|
+
"@northguild/gmt-oxlint/no-date-now": "error",
|
|
93
|
+
"@northguild/gmt-oxlint/no-date-parse": "error",
|
|
94
|
+
"@northguild/gmt-oxlint/no-date-utc": "error",
|
|
95
|
+
"@northguild/gmt-oxlint/no-date-getTimezoneOffset": "error"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Array form also supported:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
105
|
+
"jsPlugins": ["@northguild/gmt-oxlint"],
|
|
106
|
+
"rules": {
|
|
107
|
+
"@northguild/gmt-oxlint/no-date-global": ["error"],
|
|
108
|
+
"@northguild/gmt-oxlint/no-new-date": ["error"],
|
|
109
|
+
"@northguild/gmt-oxlint/no-date-now": ["error"],
|
|
110
|
+
"@northguild/gmt-oxlint/no-date-parse": ["error"],
|
|
111
|
+
"@northguild/gmt-oxlint/no-date-utc": ["error"],
|
|
112
|
+
"@northguild/gmt-oxlint/no-date-getTimezoneOffset": ["error"]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Banned patterns
|
|
118
|
+
|
|
119
|
+
| Pattern | Rule | Suggestion |
|
|
120
|
+
| -------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
121
|
+
| `Date` global reference | `no-date-global` | Use `getNow()`, `getUnixNow('milliseconds' or 'seconds')`, `getUtcNow()`, or `getZonedNow(timezone)` |
|
|
122
|
+
| `new Date(...)` | `no-new-date` | Use `getUtcNow()`, `getNow()`, or `getZonedNow(timezone)` |
|
|
123
|
+
| `Date.now()` | `no-date-now` | Use `getUnixNow('milliseconds' or 'seconds')` or `getNow()` |
|
|
124
|
+
| `Date.parse(...)` | `no-date-parse` | Use `convertZonedToUnix(value)` |
|
|
125
|
+
| `Date.UTC(...)` | `no-date-utc` | Use `convertUtcDateTimeToUnix('YYYY-MM-DDTHH:mm:ss', 'milliseconds' or 'seconds')` |
|
|
126
|
+
| `date.getTimezoneOffset()` | `no-date-getTimezoneOffset` | Use `getZonedNow(timezone)`, other gmt zoned helpers such as `convertZonedToUnix(value)`, or `Temporal.ZonedDateTime` |
|
|
127
|
+
|
|
128
|
+
## Why Temporal?
|
|
129
|
+
|
|
130
|
+
[Temporal](https://tc39.es/proposal-temporal/) solves fundamental issues with JavaScript's `Date` object:
|
|
131
|
+
|
|
132
|
+
- Immutability: no accidental mutations
|
|
133
|
+
- Timezone awareness: explicit, unambiguous timezone handling
|
|
134
|
+
- No DST bugs: proper daylight saving time logic
|
|
135
|
+
- Precision: nanosecond precision where needed
|
|
136
|
+
|
|
137
|
+
All banned Date APIs have Temporal equivalents that are safer, clearer, and more correct.
|
|
138
|
+
|
|
139
|
+
## Notes
|
|
140
|
+
|
|
141
|
+
- Oxlint JS plugins are currently in alpha.
|
|
142
|
+
- This package is authored in TypeScript and bundled with `tsup` for publishing.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare const recommended: {
|
|
2
|
+
readonly rules: {
|
|
3
|
+
readonly "@northguild/gmt-oxlint/no-date-global": "error";
|
|
4
|
+
readonly "@northguild/gmt-oxlint/no-new-date": "error";
|
|
5
|
+
readonly "@northguild/gmt-oxlint/no-date-now": "error";
|
|
6
|
+
readonly "@northguild/gmt-oxlint/no-date-parse": "error";
|
|
7
|
+
readonly "@northguild/gmt-oxlint/no-date-utc": "error";
|
|
8
|
+
readonly "@northguild/gmt-oxlint/no-date-getTimezoneOffset": "error";
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default recommended;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rules": {
|
|
3
|
+
"@northguild/gmt-oxlint/no-date-global": "error",
|
|
4
|
+
"@northguild/gmt-oxlint/no-new-date": "error",
|
|
5
|
+
"@northguild/gmt-oxlint/no-date-now": "error",
|
|
6
|
+
"@northguild/gmt-oxlint/no-date-parse": "error",
|
|
7
|
+
"@northguild/gmt-oxlint/no-date-utc": "error",
|
|
8
|
+
"@northguild/gmt-oxlint/no-date-getTimezoneOffset": "error"
|
|
9
|
+
}
|
|
10
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
type Node = {
|
|
2
|
+
type: string;
|
|
3
|
+
range?: [number, number];
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
};
|
|
6
|
+
type IdentifierNode = Node & {
|
|
7
|
+
type: "Identifier";
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
type CallExpressionNode = Node & {
|
|
11
|
+
type: "CallExpression";
|
|
12
|
+
callee: Node;
|
|
13
|
+
};
|
|
14
|
+
type NewExpressionNode = Node & {
|
|
15
|
+
type: "NewExpression";
|
|
16
|
+
callee: Node;
|
|
17
|
+
};
|
|
18
|
+
type ProgramNode = Node & {
|
|
19
|
+
type: "Program";
|
|
20
|
+
};
|
|
21
|
+
type ScopeReference = {
|
|
22
|
+
identifier: IdentifierNode;
|
|
23
|
+
};
|
|
24
|
+
type ScopeInfo = {
|
|
25
|
+
through?: ScopeReference[];
|
|
26
|
+
};
|
|
27
|
+
type SourceCodeLike = {
|
|
28
|
+
getScope(node: Node): ScopeInfo;
|
|
29
|
+
};
|
|
30
|
+
type RuleContext = {
|
|
31
|
+
sourceCode: SourceCodeLike;
|
|
32
|
+
report(input: {
|
|
33
|
+
node: Node;
|
|
34
|
+
message: string;
|
|
35
|
+
}): void;
|
|
36
|
+
};
|
|
37
|
+
type RuleListener = {
|
|
38
|
+
Program?: (node: ProgramNode) => void;
|
|
39
|
+
NewExpression?: (node: NewExpressionNode) => void;
|
|
40
|
+
CallExpression?: (node: CallExpressionNode) => void;
|
|
41
|
+
};
|
|
42
|
+
type RuleModule = {
|
|
43
|
+
meta: {
|
|
44
|
+
type: "problem" | "suggestion" | "layout";
|
|
45
|
+
docs: {
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
48
|
+
schema: unknown[];
|
|
49
|
+
};
|
|
50
|
+
create(context: RuleContext): RuleListener;
|
|
51
|
+
};
|
|
52
|
+
type OxlintPluginRuleConfig = "error" | "warn" | "off" | readonly [string] | readonly [string, unknown];
|
|
53
|
+
interface OxlintPluginConfig {
|
|
54
|
+
rules?: Record<string, OxlintPluginRuleConfig>;
|
|
55
|
+
}
|
|
56
|
+
type OxlintPlugin = {
|
|
57
|
+
meta: {
|
|
58
|
+
name: string;
|
|
59
|
+
};
|
|
60
|
+
rules: Record<string, RuleModule>;
|
|
61
|
+
configs?: Record<string, OxlintPluginConfig>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
declare const recommendedRules: {
|
|
65
|
+
readonly "@northguild/gmt-oxlint/no-date-global": "error";
|
|
66
|
+
readonly "@northguild/gmt-oxlint/no-new-date": "error";
|
|
67
|
+
readonly "@northguild/gmt-oxlint/no-date-now": "error";
|
|
68
|
+
readonly "@northguild/gmt-oxlint/no-date-parse": "error";
|
|
69
|
+
readonly "@northguild/gmt-oxlint/no-date-utc": "error";
|
|
70
|
+
readonly "@northguild/gmt-oxlint/no-date-getTimezoneOffset": "error";
|
|
71
|
+
};
|
|
72
|
+
declare const recommendedConfig: {
|
|
73
|
+
readonly jsPlugins: readonly ["@northguild/gmt-oxlint"];
|
|
74
|
+
readonly rules: {
|
|
75
|
+
readonly "@northguild/gmt-oxlint/no-date-global": "error";
|
|
76
|
+
readonly "@northguild/gmt-oxlint/no-new-date": "error";
|
|
77
|
+
readonly "@northguild/gmt-oxlint/no-date-now": "error";
|
|
78
|
+
readonly "@northguild/gmt-oxlint/no-date-parse": "error";
|
|
79
|
+
readonly "@northguild/gmt-oxlint/no-date-utc": "error";
|
|
80
|
+
readonly "@northguild/gmt-oxlint/no-date-getTimezoneOffset": "error";
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
declare const plugin: OxlintPlugin;
|
|
84
|
+
|
|
85
|
+
export { plugin as default, recommendedConfig, recommendedRules };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// src/messages.ts
|
|
2
|
+
var MSG_DATE_GLOBAL = "Avoid Date. Use @northguild/gmt getNow(), getUnixNow('milliseconds' | 'seconds'), getUtcNow(), or getZonedNow(timezone) instead.";
|
|
3
|
+
var MSG_NEW_DATE = "Avoid new Date(). Use @northguild/gmt getUtcNow(), getNow(), or getZonedNow(timezone) instead.";
|
|
4
|
+
var MSG_DATE_NOW = "Avoid Date.now(). Use @northguild/gmt getUnixNow('milliseconds' | 'seconds') or getNow() instead.";
|
|
5
|
+
var MSG_DATE_UTC = "Avoid Date.UTC(). Use @northguild/gmt convertUtcDateTimeToUnix('YYYY-MM-DDTHH:mm:ss', 'milliseconds' | 'seconds') instead.";
|
|
6
|
+
var MSG_DATE_PARSE = "Avoid Date.parse(). Use @northguild/gmt convertZonedToUnix(value) instead.";
|
|
7
|
+
var MSG_GET_TIMEZONE_OFFSET = "Avoid date.getTimezoneOffset(). Timezone offsets change throughout the year, so use @northguild/gmt zoned methods instead.";
|
|
8
|
+
|
|
9
|
+
// src/utils/isChainExpression.ts
|
|
10
|
+
function isChainExpression(node) {
|
|
11
|
+
return Boolean(node && node.type === "ChainExpression");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/utils/isMemberExpression.ts
|
|
15
|
+
function isMemberExpression(node) {
|
|
16
|
+
return Boolean(node && node.type === "MemberExpression");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/utils/getMemberExpressionFromCall.ts
|
|
20
|
+
function getMemberExpressionFromCall(node) {
|
|
21
|
+
const { callee } = node;
|
|
22
|
+
if (isMemberExpression(callee)) {
|
|
23
|
+
return callee;
|
|
24
|
+
}
|
|
25
|
+
if (isChainExpression(callee) && isMemberExpression(callee.expression)) {
|
|
26
|
+
return callee.expression;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/utils/isIdentifier.ts
|
|
32
|
+
function isIdentifier(node, name) {
|
|
33
|
+
return Boolean(
|
|
34
|
+
node && node.type === "Identifier" && node.name === name
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/utils/hasDateStaticMemberCall.ts
|
|
39
|
+
function hasDateStaticMemberCall(node, memberName) {
|
|
40
|
+
const callee = getMemberExpressionFromCall(node);
|
|
41
|
+
if (!callee || callee.computed) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return isIdentifier(callee.object, "Date") && isIdentifier(callee.property, memberName);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/rules/no-date-getTimezoneOffset.ts
|
|
48
|
+
var noDateGetTimezoneOffsetRule = {
|
|
49
|
+
meta: {
|
|
50
|
+
type: "problem",
|
|
51
|
+
docs: { description: "Disallow getTimezoneOffset()." },
|
|
52
|
+
schema: []
|
|
53
|
+
},
|
|
54
|
+
create(context) {
|
|
55
|
+
return {
|
|
56
|
+
CallExpression(node) {
|
|
57
|
+
const callee = getMemberExpressionFromCall(node);
|
|
58
|
+
if (!callee || callee.computed) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!isIdentifier(callee.property, "getTimezoneOffset")) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
context.report({ node, message: MSG_GET_TIMEZONE_OFFSET });
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/rules/no-date-global.ts
|
|
71
|
+
var reportThroughDateReferences = (context, node, message) => {
|
|
72
|
+
const sourceScope = context.sourceCode.getScope(node);
|
|
73
|
+
const through = sourceScope?.through;
|
|
74
|
+
if (!through?.length) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const seen = /* @__PURE__ */ new Set();
|
|
78
|
+
for (const ref of through) {
|
|
79
|
+
const id = ref.identifier;
|
|
80
|
+
if (!isIdentifier(id, "Date")) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const [start, end] = id.range ?? [-1, -1];
|
|
84
|
+
const key = `${start}:${end}`;
|
|
85
|
+
if (seen.has(key)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
seen.add(key);
|
|
89
|
+
context.report({ node: id, message });
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var noDateGlobalRule = {
|
|
93
|
+
meta: {
|
|
94
|
+
type: "problem",
|
|
95
|
+
docs: { description: "Disallow the global Date object." },
|
|
96
|
+
schema: []
|
|
97
|
+
},
|
|
98
|
+
create(context) {
|
|
99
|
+
return {
|
|
100
|
+
Program(node) {
|
|
101
|
+
reportThroughDateReferences(context, node, MSG_DATE_GLOBAL);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/rules/no-date-now.ts
|
|
108
|
+
var noDateNowRule = {
|
|
109
|
+
meta: {
|
|
110
|
+
type: "problem",
|
|
111
|
+
docs: { description: "Disallow Date.now()." },
|
|
112
|
+
schema: []
|
|
113
|
+
},
|
|
114
|
+
create(context) {
|
|
115
|
+
return {
|
|
116
|
+
CallExpression(node) {
|
|
117
|
+
if (!hasDateStaticMemberCall(node, "now")) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
context.report({ node, message: MSG_DATE_NOW });
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/rules/no-date-parse.ts
|
|
127
|
+
var noDateParseRule = {
|
|
128
|
+
meta: {
|
|
129
|
+
type: "problem",
|
|
130
|
+
docs: { description: "Disallow Date.parse()." },
|
|
131
|
+
schema: []
|
|
132
|
+
},
|
|
133
|
+
create(context) {
|
|
134
|
+
return {
|
|
135
|
+
CallExpression(node) {
|
|
136
|
+
if (!hasDateStaticMemberCall(node, "parse")) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
context.report({ node, message: MSG_DATE_PARSE });
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/rules/no-date-utc.ts
|
|
146
|
+
var noDateUtcRule = {
|
|
147
|
+
meta: {
|
|
148
|
+
type: "problem",
|
|
149
|
+
docs: { description: "Disallow Date.UTC()." },
|
|
150
|
+
schema: []
|
|
151
|
+
},
|
|
152
|
+
create(context) {
|
|
153
|
+
return {
|
|
154
|
+
CallExpression(node) {
|
|
155
|
+
if (!hasDateStaticMemberCall(node, "UTC")) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
context.report({ node, message: MSG_DATE_UTC });
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// src/rules/no-new-date.ts
|
|
165
|
+
var noNewDateRule = {
|
|
166
|
+
meta: {
|
|
167
|
+
type: "problem",
|
|
168
|
+
docs: { description: "Disallow new Date(...)." },
|
|
169
|
+
schema: []
|
|
170
|
+
},
|
|
171
|
+
create(context) {
|
|
172
|
+
return {
|
|
173
|
+
NewExpression(node) {
|
|
174
|
+
if (!isIdentifier(node.callee, "Date")) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
context.report({ node, message: MSG_NEW_DATE });
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// src/index.ts
|
|
184
|
+
var recommendedRules = {
|
|
185
|
+
"@northguild/gmt-oxlint/no-date-global": "error",
|
|
186
|
+
"@northguild/gmt-oxlint/no-new-date": "error",
|
|
187
|
+
"@northguild/gmt-oxlint/no-date-now": "error",
|
|
188
|
+
"@northguild/gmt-oxlint/no-date-parse": "error",
|
|
189
|
+
"@northguild/gmt-oxlint/no-date-utc": "error",
|
|
190
|
+
"@northguild/gmt-oxlint/no-date-getTimezoneOffset": "error"
|
|
191
|
+
};
|
|
192
|
+
var recommendedConfig = {
|
|
193
|
+
jsPlugins: ["@northguild/gmt-oxlint"],
|
|
194
|
+
rules: recommendedRules
|
|
195
|
+
};
|
|
196
|
+
var plugin = {
|
|
197
|
+
meta: { name: "@northguild/gmt-oxlint" },
|
|
198
|
+
rules: {
|
|
199
|
+
"no-date-global": noDateGlobalRule,
|
|
200
|
+
"no-new-date": noNewDateRule,
|
|
201
|
+
"no-date-now": noDateNowRule,
|
|
202
|
+
"no-date-parse": noDateParseRule,
|
|
203
|
+
"no-date-utc": noDateUtcRule,
|
|
204
|
+
"no-date-getTimezoneOffset": noDateGetTimezoneOffsetRule
|
|
205
|
+
},
|
|
206
|
+
configs: { recommended: { rules: recommendedRules } }
|
|
207
|
+
};
|
|
208
|
+
var index_default = plugin;
|
|
209
|
+
export {
|
|
210
|
+
index_default as default,
|
|
211
|
+
recommendedConfig,
|
|
212
|
+
recommendedRules
|
|
213
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@northguild/gmt-oxlint",
|
|
3
|
+
"version": "1.1.5",
|
|
4
|
+
"description": "Oxlint JS plugin for banning Date APIs in Temporal-first projects",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"date",
|
|
7
|
+
"eslint-plugin",
|
|
8
|
+
"lint",
|
|
9
|
+
"oxlint",
|
|
10
|
+
"tanstack-intent",
|
|
11
|
+
"temporal"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/northguild/gmt#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/northguild/gmt/issues"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"contributors": [
|
|
19
|
+
{
|
|
20
|
+
"name": "Baldur Páll Hólmgeirsson",
|
|
21
|
+
"url": "https://github.com/baldurpan"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "Craig O. Curtis",
|
|
25
|
+
"url": "https://github.com/craig-o-curtis"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/northguild/gmt.git",
|
|
31
|
+
"directory": "packages/gmt-oxlint"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"config",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"skills"
|
|
39
|
+
],
|
|
40
|
+
"type": "module",
|
|
41
|
+
"main": "./dist/index.js",
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"exports": {
|
|
44
|
+
".": {
|
|
45
|
+
"types": "./dist/index.d.ts",
|
|
46
|
+
"default": "./dist/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./recommended": {
|
|
49
|
+
"types": "./config/recommended.d.ts",
|
|
50
|
+
"default": "./config/recommended.json"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public",
|
|
55
|
+
"registry": "https://registry.npmjs.org/"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@tanstack/intent": "^0.3.6",
|
|
59
|
+
"oxlint": "^1.0.0",
|
|
60
|
+
"tsup": "^8.5.0",
|
|
61
|
+
"typescript": "^5.0.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"oxlint": ">=1.0.0"
|
|
65
|
+
},
|
|
66
|
+
"oxlint": "./dist/index.js",
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "tsup",
|
|
69
|
+
"test": "vitest run --config vitest.config.ts",
|
|
70
|
+
"test:watch": "vitest --config vitest.config.ts",
|
|
71
|
+
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
72
|
+
"pack": "npm pack --dry-run",
|
|
73
|
+
"publish:dry-run": "npm publish --access public --dry-run",
|
|
74
|
+
"publish:public": "npm publish --access public"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ci-enforcement
|
|
3
|
+
description: >
|
|
4
|
+
Enforce @northguild/gmt-oxlint Date-ban rules in CI with staged rollout, clear
|
|
5
|
+
gmt-oxlint/* diagnostics, and policy alignment with ESLint/Biome where
|
|
6
|
+
multiple linters are used.
|
|
7
|
+
metadata:
|
|
8
|
+
library_version: 1.1.2
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# CI Enforcement
|
|
12
|
+
|
|
13
|
+
Use this skill when enabling Oxlint-based Date policy checks in CI.
|
|
14
|
+
|
|
15
|
+
## CI rollout
|
|
16
|
+
|
|
17
|
+
1. Add Oxlint lint step
|
|
18
|
+
- Ensure pull requests run Oxlint with gmt-oxlint plugin rules enabled.
|
|
19
|
+
|
|
20
|
+
2. Stage adoption for large repos
|
|
21
|
+
- Start with changed files or specific paths if baseline debt is high.
|
|
22
|
+
- Expand to full repository enforcement once baseline is controlled.
|
|
23
|
+
|
|
24
|
+
3. Keep diagnostics actionable
|
|
25
|
+
- Ensure CI output clearly reports `@northguild/gmt-oxlint/*` rule failures and paths.
|
|
26
|
+
|
|
27
|
+
4. Align multi-linter policy
|
|
28
|
+
- If ESLint/Biome are also enabled, keep Date-ban expectations consistent.
|
|
29
|
+
|
|
30
|
+
## Recommended sequence
|
|
31
|
+
|
|
32
|
+
1. Local baseline run.
|
|
33
|
+
2. PR enforcement.
|
|
34
|
+
3. Main branch full coverage.
|
|
35
|
+
4. Ongoing remediation through gmt-guided refactor suggestions.
|
|
36
|
+
|
|
37
|
+
## Guardrails
|
|
38
|
+
|
|
39
|
+
- Do not rely on broad suppression as default.
|
|
40
|
+
- Do not auto-apply risky time/date changes without tests.
|
|
41
|
+
- Preserve developer velocity with phased rollout where needed.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: first-time-setup
|
|
3
|
+
description: >
|
|
4
|
+
Install and configure @northguild/gmt-oxlint with the simplest consumer DX,
|
|
5
|
+
prefer oxlint.config.ts + recommendedConfig import, and verify gmt-oxlint
|
|
6
|
+
diagnostics while preserving current repository lint behavior.
|
|
7
|
+
metadata:
|
|
8
|
+
library_version: 1.1.2
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# First-Time Setup
|
|
12
|
+
|
|
13
|
+
Use this skill when a user wants to adopt `@northguild/gmt-oxlint`.
|
|
14
|
+
|
|
15
|
+
## Setup flow
|
|
16
|
+
|
|
17
|
+
1. Install dependencies
|
|
18
|
+
- Install `@northguild/gmt-oxlint` and `oxlint` as dev dependencies.
|
|
19
|
+
|
|
20
|
+
2. Configure Oxlint (prefer TypeScript config for best DX)
|
|
21
|
+
- Prefer `oxlint.config.ts` with:
|
|
22
|
+
- `import { defineConfig } from "oxlint"`
|
|
23
|
+
- `import { recommendedConfig } from "@northguild/gmt-oxlint"`
|
|
24
|
+
- `export default defineConfig(recommendedConfig)`
|
|
25
|
+
- If using `.oxlintrc.json`, add `@northguild/gmt-oxlint` to `jsPlugins` and use path-based extends:
|
|
26
|
+
- `extends: ["./node_modules/@northguild/gmt-oxlint/config/recommended.json"]`
|
|
27
|
+
- Do not use named shared config specifiers in JSON `extends` (for example `@northguild/gmt-oxlint/recommended`); Oxlint currently resolves `extends` as file paths.
|
|
28
|
+
- Keep unrelated existing rules unless the user requests consolidation.
|
|
29
|
+
|
|
30
|
+
3. Verify enforcement
|
|
31
|
+
- Run Oxlint and confirm Date API bans are reported.
|
|
32
|
+
- Confirm rule IDs are emitted as `@northguild/gmt-oxlint/*`.
|
|
33
|
+
|
|
34
|
+
4. Optional aliasing
|
|
35
|
+
- If the user wants shorter rule IDs, use object-form `jsPlugins` with `name` alias and configure rules under that alias.
|
|
36
|
+
- Keep the default package-name namespace unless a shorter alias is explicitly requested.
|
|
37
|
+
|
|
38
|
+
5. Suggest companion packages
|
|
39
|
+
- If `@northguild/gmt` is missing, suggest installing it for safe Date-to-Temporal refactors.
|
|
40
|
+
- If ESLint or Biome are already used, optionally suggest matching gmt lint packages for policy consistency.
|
|
41
|
+
|
|
42
|
+
## Guardrails
|
|
43
|
+
|
|
44
|
+
- Keep setup additive and low-risk.
|
|
45
|
+
- Avoid broad config rewrites.
|
|
46
|
+
- Respect that developers may use one linter package or combine multiple.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migration-refactor
|
|
3
|
+
description: >
|
|
4
|
+
Resolve @northguild/gmt-oxlint Date-rule violations by preferring existing gmt
|
|
5
|
+
helpers, preserving behavior, and guiding the Temporal custom-method path when
|
|
6
|
+
gmt does not yet provide a needed helper.
|
|
7
|
+
metadata:
|
|
8
|
+
library_version: 1.1.2
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Migration Refactor
|
|
12
|
+
|
|
13
|
+
Use this skill when Oxlint reports Date API violations and the user asks for fixes.
|
|
14
|
+
|
|
15
|
+
## Refactor policy
|
|
16
|
+
|
|
17
|
+
1. Prefer existing gmt helpers
|
|
18
|
+
- Replace Date APIs with available `@northguild/gmt` methods first.
|
|
19
|
+
|
|
20
|
+
2. Preserve behavior
|
|
21
|
+
- Keep output format, timezone intent, and edge-case semantics stable.
|
|
22
|
+
- Avoid risky broad rewrites if focused edits are sufficient.
|
|
23
|
+
|
|
24
|
+
3. If helper is missing
|
|
25
|
+
- Explain the behavior is possible through Temporal (shipped by gmt).
|
|
26
|
+
- Clarify this requires a new custom gmt method.
|
|
27
|
+
- Recommend opening a GitHub issue with examples and expected behavior.
|
|
28
|
+
- If implemented now, include robust tests and suggest opening a PR.
|
|
29
|
+
|
|
30
|
+
## Common replacement direction
|
|
31
|
+
|
|
32
|
+
- `Date` global usage -> explicit gmt get/convert/parse flows.
|
|
33
|
+
- `new Date(...)` -> gmt plain/zoned parsing + conversion where available.
|
|
34
|
+
- `Date.now()` -> `getUnixNow(...)` or `getNow()` based on expected shape.
|
|
35
|
+
- `Date.parse(...)` -> gmt conversion helper where available.
|
|
36
|
+
- `Date.UTC(...)` -> gmt UTC conversion helper where available.
|
|
37
|
+
- `getTimezoneOffset()` usage -> timezone-aware gmt zoned helpers.
|
|
38
|
+
|
|
39
|
+
## Validation
|
|
40
|
+
|
|
41
|
+
- Re-run Oxlint and related tests.
|
|
42
|
+
- Verify invalid-input fallback behavior remains compliant.
|