eslint-plugin-conventions 4.2.5 → 4.2.7

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.
@@ -1,159 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noJsonSchemaTags = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- const DEFAULT_FORBIDDEN_TAGS = new Set([
6
- 'minimum',
7
- 'maximum',
8
- 'exclusiveMinimum',
9
- 'exclusiveMaximum',
10
- 'minLength',
11
- 'maxLength',
12
- 'minItems',
13
- 'maxItems',
14
- 'minProperties',
15
- 'maxProperties',
16
- 'multipleOf',
17
- 'pattern',
18
- 'uniqueItems',
19
- 'const',
20
- 'enum',
21
- 'format',
22
- 'contentMediaType',
23
- 'contentEncoding',
24
- ]);
25
- function findForbiddenTags(commentText, forbiddenSet) {
26
- const results = [];
27
- const tagRegex = /@([a-zA-Z][a-zA-Z0-9]*)\b/g;
28
- let match;
29
- while ((match = tagRegex.exec(commentText)) !== null) {
30
- const tagName = match[1];
31
- if (forbiddenSet.has(tagName)) {
32
- results.push({ tag: tagName, offset: match.index });
33
- }
34
- }
35
- return results;
36
- }
37
- function getSuggestionText(tag) {
38
- const suggestions = {
39
- minimum: 'Include range in description, e.g. "(min: 1)" or "Range: 1-100"',
40
- maximum: 'Include range in description, e.g. "(max: 100)" or "Range: 1-100"',
41
- exclusiveMinimum: 'Include constraint in description, e.g. "(> 0)"',
42
- exclusiveMaximum: 'Include constraint in description, e.g. "(< 100)"',
43
- minLength: 'Include length constraint in description, e.g. "(min length: 1)"',
44
- maxLength: 'Include length constraint in description, e.g. "(max length: 255)"',
45
- minItems: 'Include item count in description, e.g. "(at least 1 item)"',
46
- maxItems: 'Include item count in description, e.g. "(at most 10 items)"',
47
- pattern: 'Include the expected format in description text',
48
- enum: 'Use @typedef or list allowed values in description',
49
- const: 'Use @default instead, or document the fixed value in description',
50
- format: 'Describe the expected format in description, e.g. "(ISO 8601 date)"',
51
- };
52
- return (suggestions[tag] ||
53
- 'Move this constraint information into the description text');
54
- }
55
- exports.noJsonSchemaTags = (0, eslint_devkit_1.createRule)({
56
- name: 'no-json-schema-tags',
57
- meta: {
58
- type: 'suggestion',
59
- docs: {
60
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-json-schema-tags.md',
61
- description: 'Disallow JSON Schema keywords used as JSDoc tags (e.g. @minimum, @maximum)',
62
- },
63
- hasSuggestions: true,
64
- messages: {
65
- jsonSchemaTag: (0, eslint_devkit_1.formatLLMMessage)({
66
- icon: eslint_devkit_1.MessageIcons.WARNING,
67
- issueName: 'JSON Schema Tag in JSDoc',
68
- description: '`@{{tag}}` is a JSON Schema keyword, not a valid JSDoc tag. It will cause CI failures in projects with strict tag validation (e.g. DefinitelyTyped). {{suggestion}}',
69
- severity: 'MEDIUM',
70
- fix: 'Remove the @{{tag}} tag and express the constraint in the description text instead',
71
- documentationLink: 'https://jsdoc.app/about-block-inline-tags',
72
- }),
73
- moveToDescription: (0, eslint_devkit_1.formatLLMMessage)({
74
- icon: eslint_devkit_1.MessageIcons.INFO,
75
- issueName: 'Move to Description Text',
76
- description: 'Remove the `@{{tag}}` tag and add the constraint directly to the description',
77
- severity: 'LOW',
78
- fix: 'Delete the @{{tag}} line and include the information in the type description',
79
- documentationLink: 'https://jsdoc.app/about-block-inline-tags',
80
- }),
81
- },
82
- schema: [
83
- {
84
- type: 'object',
85
- properties: {
86
- additionalForbiddenTags: {
87
- type: 'array',
88
- items: { type: 'string' },
89
- default: [],
90
- description: 'Additional tag names to forbid',
91
- },
92
- },
93
- additionalProperties: false,
94
- },
95
- ],
96
- },
97
- defaultOptions: [{ additionalForbiddenTags: [] }],
98
- create(context, [options = {}]) {
99
- const { additionalForbiddenTags = [] } = options;
100
- const forbiddenTags = new Set(DEFAULT_FORBIDDEN_TAGS);
101
- for (const tag of additionalForbiddenTags) {
102
- forbiddenTags.add(tag);
103
- }
104
- const sourceCode = context.sourceCode;
105
- return {
106
- Program() {
107
- const comments = sourceCode.getAllComments();
108
- for (const comment of comments) {
109
- if (comment.type !== 'Block') {
110
- continue;
111
- }
112
- const commentText = comment.value;
113
- const hits = findForbiddenTags(commentText, forbiddenTags);
114
- for (const { tag, offset } of hits) {
115
- const absoluteStart = comment
116
- .range[0] +
117
- 2 +
118
- offset;
119
- const absoluteEnd = absoluteStart + 1 + tag.length;
120
- const suggestion = getSuggestionText(tag);
121
- context.report({
122
- loc: {
123
- start: sourceCode.getLocFromIndex(absoluteStart),
124
- end: sourceCode.getLocFromIndex(absoluteEnd),
125
- },
126
- messageId: 'jsonSchemaTag',
127
- data: {
128
- tag,
129
- suggestion,
130
- },
131
- suggest: [
132
- {
133
- messageId: 'moveToDescription',
134
- data: { tag },
135
- fix: (fixer) => {
136
- const fullSource = sourceCode.getText();
137
- let lineStart = absoluteStart;
138
- while (lineStart > 0 && fullSource[lineStart - 1] !== '\n') {
139
- lineStart--;
140
- }
141
- let lineEnd = absoluteEnd;
142
- while (lineEnd < fullSource.length &&
143
- fullSource[lineEnd] !== '\n') {
144
- lineEnd++;
145
- }
146
- if (lineEnd < fullSource.length && fullSource[lineEnd] === '\n') {
147
- lineEnd++;
148
- }
149
- return fixer.removeRange([lineStart, lineEnd]);
150
- },
151
- },
152
- ],
153
- });
154
- }
155
- }
156
- },
157
- };
158
- },
159
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noJsonSchemaTags=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const DEFAULT_FORBIDDEN_TAGS=new Set(["minimum","maximum","exclusiveMinimum","exclusiveMaximum","minLength","maxLength","minItems","maxItems","minProperties","maxProperties","multipleOf","pattern","uniqueItems","const","enum","format","contentMediaType","contentEncoding"]);function findForbiddenTags(commentText,forbiddenSet){const results=[];const tagRegex=/@([a-zA-Z][a-zA-Z0-9]*)\b/g;let match;while((match=tagRegex.exec(commentText))!==null){const tagName=match[1];if(forbiddenSet.has(tagName)){results.push({tag:tagName,offset:match.index})}}return results}function getSuggestionText(tag){const suggestions={minimum:'Include range in description, e.g. "(min: 1)" or "Range: 1-100"',maximum:'Include range in description, e.g. "(max: 100)" or "Range: 1-100"',exclusiveMinimum:'Include constraint in description, e.g. "(> 0)"',exclusiveMaximum:'Include constraint in description, e.g. "(< 100)"',minLength:'Include length constraint in description, e.g. "(min length: 1)"',maxLength:'Include length constraint in description, e.g. "(max length: 255)"',minItems:'Include item count in description, e.g. "(at least 1 item)"',maxItems:'Include item count in description, e.g. "(at most 10 items)"',pattern:"Include the expected format in description text",enum:"Use @typedef or list allowed values in description",const:"Use @default instead, or document the fixed value in description",format:'Describe the expected format in description, e.g. "(ISO 8601 date)"'};return suggestions[tag]||"Move this constraint information into the description text"}exports.noJsonSchemaTags=(0,eslint_devkit_1.createRule)({name:"no-json-schema-tags",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-json-schema-tags.md",description:"Disallow JSON Schema keywords used as JSDoc tags (e.g. @minimum, @maximum)"},hasSuggestions:true,messages:{jsonSchemaTag:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"JSON Schema Tag in JSDoc",description:"`@{{tag}}` is a JSON Schema keyword, not a valid JSDoc tag. It will cause CI failures in projects with strict tag validation (e.g. DefinitelyTyped). {{suggestion}}",severity:"MEDIUM",fix:"Remove the @{{tag}} tag and express the constraint in the description text instead",documentationLink:"https://jsdoc.app/about-block-inline-tags"}),moveToDescription:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Move to Description Text",description:"Remove the `@{{tag}}` tag and add the constraint directly to the description",severity:"LOW",fix:"Delete the @{{tag}} line and include the information in the type description",documentationLink:"https://jsdoc.app/about-block-inline-tags"})},schema:[{type:"object",properties:{additionalForbiddenTags:{type:"array",items:{type:"string"},default:[],description:"Additional tag names to forbid"}},additionalProperties:false}]},defaultOptions:[{additionalForbiddenTags:[]}],create(context,[options={}]){const{additionalForbiddenTags=[]}=options;const forbiddenTags=new Set(DEFAULT_FORBIDDEN_TAGS);for(const tag of additionalForbiddenTags){forbiddenTags.add(tag)}const sourceCode=context.sourceCode;return{Program(){const comments=sourceCode.getAllComments();for(const comment of comments){if(comment.type!=="Block"){continue}const commentText=comment.value;const hits=findForbiddenTags(commentText,forbiddenTags);for(const{tag,offset}of hits){const absoluteStart=comment.range[0]+2+offset;const absoluteEnd=absoluteStart+1+tag.length;const suggestion=getSuggestionText(tag);context.report({loc:{start:sourceCode.getLocFromIndex(absoluteStart),end:sourceCode.getLocFromIndex(absoluteEnd)},messageId:"jsonSchemaTag",data:{tag,suggestion},suggest:[{messageId:"moveToDescription",data:{tag},fix:fixer=>{const fullSource=sourceCode.getText();let lineStart=absoluteStart;while(lineStart>0&&fullSource[lineStart-1]!=="\n"){lineStart--}let lineEnd=absoluteEnd;while(lineEnd<fullSource.length&&fullSource[lineEnd]!=="\n"){lineEnd++}if(lineEnd<fullSource.length&&fullSource[lineEnd]==="\n"){lineEnd++}return fixer.removeRange([lineStart,lineEnd])}}]})}}}}}});
@@ -1,185 +1,2 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noMagicNumbers = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- const eslint_devkit_2 = require("@interlace/eslint-devkit");
6
- const DEFAULT_IGNORE = new Set([-1, 0, 1, 2]);
7
- function constNameFor(value) {
8
- const prefix = value < 0 ? 'MAGIC_NEG_' : 'MAGIC_';
9
- const digits = String(Math.abs(value)).replace('.', '_');
10
- return `${prefix}${digits}`;
11
- }
12
- function nearestStatement(node) {
13
- const STATEMENT_TYPES = new Set([
14
- 'ExpressionStatement', 'VariableDeclaration', 'ReturnStatement',
15
- 'IfStatement', 'WhileStatement', 'ForStatement', 'ForInStatement',
16
- 'ForOfStatement', 'ThrowStatement', 'SwitchStatement',
17
- ]);
18
- let current = node.parent;
19
- while (current) {
20
- if (STATEMENT_TYPES.has(current.type))
21
- return current;
22
- current = current.parent;
23
- }
24
- return null;
25
- }
26
- exports.noMagicNumbers = (0, eslint_devkit_1.createRule)({
27
- name: 'no-magic-numbers',
28
- meta: {
29
- type: 'suggestion',
30
- docs: {
31
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-magic-numbers.md',
32
- description: 'Disallow magic numbers (numeric literals without a named constant)',
33
- },
34
- hasSuggestions: true,
35
- messages: {
36
- noMagicNumber: (0, eslint_devkit_2.formatLLMMessage)({
37
- icon: eslint_devkit_2.MessageIcons.INFO,
38
- issueName: 'Magic Number',
39
- description: 'The number {{value}} is a magic literal. Extract it to a named constant to make the intent clear.',
40
- severity: 'LOW',
41
- fix: 'const TIMEOUT_MS = {{value}}; // use the named constant everywhere',
42
- documentationLink: 'https://refactoring.guru/replace-magic-literal',
43
- }),
44
- extractConst: 'Extract {{value}} to a named constant ({{constName}})',
45
- },
46
- schema: [
47
- {
48
- type: 'object',
49
- properties: {
50
- ignore: {
51
- type: 'array',
52
- items: { type: 'number' },
53
- description: 'Additional numbers to allow',
54
- },
55
- ignoreArrayIndexes: { type: 'boolean', default: true },
56
- ignoreDefaultValues: { type: 'boolean', default: true },
57
- ignoreEnums: { type: 'boolean', default: true },
58
- ignoreBitwiseExpressions: { type: 'boolean', default: false },
59
- },
60
- additionalProperties: false,
61
- },
62
- ],
63
- },
64
- defaultOptions: [
65
- {
66
- ignore: [],
67
- ignoreArrayIndexes: true,
68
- ignoreDefaultValues: true,
69
- ignoreEnums: true,
70
- ignoreBitwiseExpressions: false,
71
- },
72
- ],
73
- create(context) {
74
- const [options = {}] = context.options;
75
- const { ignore = [], ignoreArrayIndexes = true, ignoreDefaultValues = true, ignoreEnums = true, ignoreBitwiseExpressions = false, } = options;
76
- const ignoredValues = new Set([...DEFAULT_IGNORE, ...ignore]);
77
- function isIgnoredNumber(value) {
78
- return ignoredValues.has(value);
79
- }
80
- function isArrayIndex(node) {
81
- if (!ignoreArrayIndexes)
82
- return false;
83
- const parent = node.parent;
84
- return (parent?.type === 'MemberExpression' &&
85
- parent.computed &&
86
- parent.property === node);
87
- }
88
- function isDefaultValue(node) {
89
- if (!ignoreDefaultValues)
90
- return false;
91
- const parent = node.parent;
92
- return (parent?.type === 'AssignmentPattern' &&
93
- parent.right === node);
94
- }
95
- function isEnumMember(node) {
96
- if (!ignoreEnums)
97
- return false;
98
- const parent = node.parent;
99
- return parent?.type === 'TSEnumMember';
100
- }
101
- function isBitwiseContext(node) {
102
- if (!ignoreBitwiseExpressions)
103
- return false;
104
- const parent = node.parent;
105
- const BITWISE_OPS = new Set(['&', '|', '^', '<<', '>>', '>>>']);
106
- return (parent?.type === 'BinaryExpression' &&
107
- BITWISE_OPS.has(parent.operator));
108
- }
109
- function isVariableDeclarator(node) {
110
- const parent = node.parent;
111
- return parent?.type === 'VariableDeclarator';
112
- }
113
- function isExportedConst(node) {
114
- let current = node;
115
- while (current) {
116
- if (current.type === 'ExportNamedDeclaration')
117
- return true;
118
- if (current.type === 'VariableDeclaration' ||
119
- current.type === 'VariableDeclarator') {
120
- current = current.parent;
121
- continue;
122
- }
123
- break;
124
- }
125
- return false;
126
- }
127
- function isPropertyKey(node) {
128
- const parent = node.parent;
129
- return (parent?.type === 'Property' &&
130
- parent.key === node);
131
- }
132
- return {
133
- Literal(node) {
134
- if (typeof node.value !== 'number')
135
- return;
136
- const value = node.value;
137
- if (isIgnoredNumber(value))
138
- return;
139
- if (!isFinite(value))
140
- return;
141
- if (isVariableDeclarator(node))
142
- return;
143
- if (isExportedConst(node))
144
- return;
145
- if (isArrayIndex(node))
146
- return;
147
- if (isDefaultValue(node))
148
- return;
149
- if (isEnumMember(node))
150
- return;
151
- if (isBitwiseContext(node))
152
- return;
153
- if (isPropertyKey(node))
154
- return;
155
- const constName = constNameFor(value);
156
- const sourceCode = context.sourceCode;
157
- context.report({
158
- node,
159
- messageId: 'noMagicNumber',
160
- data: { value: String(value) },
161
- suggest: [
162
- {
163
- messageId: 'extractConst',
164
- data: { value: String(value), constName },
165
- fix(fixer) {
166
- const stmt = nearestStatement(node);
167
- if (!stmt)
168
- return null;
169
- const firstToken = sourceCode.getFirstToken(stmt);
170
- if (!firstToken)
171
- return null;
172
- const col = firstToken.loc.start.column;
173
- const indent = ' '.repeat(col);
174
- return [
175
- fixer.insertTextBefore(stmt, `const ${constName} = ${value};\n${indent}`),
176
- fixer.replaceText(node, constName),
177
- ];
178
- },
179
- },
180
- ],
181
- });
182
- },
183
- };
184
- },
185
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMagicNumbers=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const DEFAULT_IGNORE=new Set([-1,0,1,2]);function constNameFor(value){const prefix=value<0?"MAGIC_NEG_":"MAGIC_";const digits=String(Math.abs(value)).replace(".","_");return`${prefix}${digits}`}function nearestStatement(node){const STATEMENT_TYPES=new Set(["ExpressionStatement","VariableDeclaration","ReturnStatement","IfStatement","WhileStatement","ForStatement","ForInStatement","ForOfStatement","ThrowStatement","SwitchStatement"]);let current=node.parent;while(current){if(STATEMENT_TYPES.has(current.type))return current;current=current.parent}return null}exports.noMagicNumbers=(0,eslint_devkit_1.createRule)({name:"no-magic-numbers",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-magic-numbers.md",description:"Disallow magic numbers (numeric literals without a named constant)"},hasSuggestions:true,messages:{noMagicNumber:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.INFO,issueName:"Magic Number",description:"The number {{value}} is a magic literal. Extract it to a named constant to make the intent clear.",severity:"LOW",fix:"const TIMEOUT_MS = {{value}}; // use the named constant everywhere",documentationLink:"https://refactoring.guru/replace-magic-literal"}),extractConst:"Extract {{value}} to a named constant ({{constName}})"},schema:[{type:"object",properties:{ignore:{type:"array",items:{type:"number"},description:"Additional numbers to allow"},ignoreArrayIndexes:{type:"boolean",default:true},ignoreDefaultValues:{type:"boolean",default:true},ignoreEnums:{type:"boolean",default:true},ignoreBitwiseExpressions:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{ignore:[],ignoreArrayIndexes:true,ignoreDefaultValues:true,ignoreEnums:true,ignoreBitwiseExpressions:false}],create(context){const[options={}]=context.options;const{ignore=[],ignoreArrayIndexes=true,ignoreDefaultValues=true,ignoreEnums=true,ignoreBitwiseExpressions=false}=options;const ignoredValues=new Set([...DEFAULT_IGNORE,...ignore]);function isIgnoredNumber(value){return ignoredValues.has(value)}function isArrayIndex(node){if(!ignoreArrayIndexes)return false;const parent=node.parent;return parent?.type==="MemberExpression"&&parent.computed&&parent.property===node}function isDefaultValue(node){if(!ignoreDefaultValues)return false;const parent=node.parent;return parent?.type==="AssignmentPattern"&&parent.right===node}function isEnumMember(node){if(!ignoreEnums)return false;const parent=node.parent;return parent?.type==="TSEnumMember"}function isBitwiseContext(node){if(!ignoreBitwiseExpressions)return false;const parent=node.parent;const BITWISE_OPS=new Set(["&","|","^","<<",">>",">>>"]);return parent?.type==="BinaryExpression"&&BITWISE_OPS.has(parent.operator)}function isVariableDeclarator(node){const parent=node.parent;return parent?.type==="VariableDeclarator"}function isExportedConst(node){let current=node;while(current){if(current.type==="ExportNamedDeclaration")return true;if(current.type==="VariableDeclaration"||current.type==="VariableDeclarator"){current=current.parent;continue}break}return false}function isPropertyKey(node){const parent=node.parent;return parent?.type==="Property"&&parent.key===node}return{Literal(node){if(typeof node.value!=="number")return;const value=node.value;if(isIgnoredNumber(value))return;if(!isFinite(value))return;if(isVariableDeclarator(node))return;if(isExportedConst(node))return;if(isArrayIndex(node))return;if(isDefaultValue(node))return;if(isEnumMember(node))return;if(isBitwiseContext(node))return;if(isPropertyKey(node))return;const constName=constNameFor(value);const sourceCode=context.sourceCode;context.report({node,messageId:"noMagicNumber",data:{value:String(value)},suggest:[{messageId:"extractConst",data:{value:String(value),constName},fix(fixer){const stmt=nearestStatement(node);if(!stmt)return null;const firstToken=sourceCode.getFirstToken(stmt);if(!firstToken)return null;const col=firstToken.loc.start.column;const indent=" ".repeat(col);return[fixer.insertTextBefore(stmt,`const ${constName} = ${value};
2
+ ${indent}`),fixer.replaceText(node,constName)]}}]})}}}});
@@ -1,76 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noRawCrossPropertyHref = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- const eslint_devkit_2 = require("@interlace/eslint-devkit");
6
- const CROSS_PROPERTY_HOSTS = new Set([
7
- 'ofriperetz.dev',
8
- 'interlace.tools',
9
- 'eslint.interlace.tools',
10
- 'serverless.interlace.tools',
11
- 'ds.interlace.tools',
12
- 'storybook.interlace.tools',
13
- ]);
14
- exports.noRawCrossPropertyHref = (0, eslint_devkit_1.createRule)({
15
- name: 'no-raw-cross-property-href',
16
- meta: {
17
- type: 'problem',
18
- docs: {
19
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-raw-cross-property-href.md',
20
- description: 'Forbid hand-written cross-property hrefs; use buildUtmHref() from lib/utm.ts',
21
- },
22
- fixable: undefined,
23
- hasSuggestions: false,
24
- messages: {
25
- rawCrossPropertyHref: (0, eslint_devkit_2.formatLLMMessage)({
26
- icon: eslint_devkit_2.MessageIcons.WARNING,
27
- issueName: 'Raw cross-property href',
28
- description: 'href "{{href}}" points to a cross-property surface ({{host}}). Use buildUtmHref() from lib/utm.ts so the link carries UTM and ph_distinct_id.',
29
- severity: 'HIGH',
30
- fix: 'Replace with buildUtmHref(\'{{href}}\', { source, medium, campaign, content }).',
31
- documentationLink: 'https://github.com/ofri-peretz/eslint/blob/main/UTM_PHILOSOPHY.md',
32
- }),
33
- },
34
- schema: [],
35
- },
36
- defaultOptions: [],
37
- create(context) {
38
- function parseHost(value) {
39
- if (!/^https?:\/\//i.test(value))
40
- return null;
41
- try {
42
- return new URL(value).hostname.toLowerCase();
43
- }
44
- catch {
45
- return null;
46
- }
47
- }
48
- function isCrossPropertyHref(href) {
49
- const host = parseHost(href);
50
- if (!host)
51
- return null;
52
- if (!CROSS_PROPERTY_HOSTS.has(host))
53
- return null;
54
- return { host };
55
- }
56
- return {
57
- JSXAttribute(node) {
58
- if (node.name.type !== 'JSXIdentifier' ||
59
- node.name.name !== 'href' ||
60
- !node.value ||
61
- node.value.type !== 'Literal' ||
62
- typeof node.value.value !== 'string') {
63
- return;
64
- }
65
- const result = isCrossPropertyHref(node.value.value);
66
- if (!result)
67
- return;
68
- context.report({
69
- node: node.value,
70
- messageId: 'rawCrossPropertyHref',
71
- data: { href: node.value.value, host: result.host },
72
- });
73
- },
74
- };
75
- },
76
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noRawCrossPropertyHref=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const CROSS_PROPERTY_HOSTS=new Set(["ofriperetz.dev","interlace.tools","eslint.interlace.tools","serverless.interlace.tools","ds.interlace.tools","storybook.interlace.tools"]);exports.noRawCrossPropertyHref=(0,eslint_devkit_1.createRule)({name:"no-raw-cross-property-href",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-raw-cross-property-href.md",description:"Forbid hand-written cross-property hrefs; use buildUtmHref() from lib/utm.ts"},fixable:void 0,hasSuggestions:false,messages:{rawCrossPropertyHref:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Raw cross-property href",description:'href "{{href}}" points to a cross-property surface ({{host}}). Use buildUtmHref() from lib/utm.ts so the link carries UTM and ph_distinct_id.',severity:"HIGH",fix:"Replace with buildUtmHref('{{href}}', { source, medium, campaign, content }).",documentationLink:"https://github.com/ofri-peretz/eslint/blob/main/UTM_PHILOSOPHY.md"})},schema:[]},defaultOptions:[],create(context){function parseHost(value){if(!/^https?:\/\//i.test(value))return null;try{return new URL(value).hostname.toLowerCase()}catch{return null}}function isCrossPropertyHref(href){const host=parseHost(href);if(!host)return null;if(!CROSS_PROPERTY_HOSTS.has(host))return null;return{host}}return{JSXAttribute(node){if(node.name.type!=="JSXIdentifier"||node.name.name!=="href"||!node.value||node.value.type!=="Literal"||typeof node.value.value!=="string"){return}const result=isCrossPropertyHref(node.value.value);if(!result)return;context.report({node:node.value,messageId:"rawCrossPropertyHref",data:{href:node.value.value,host:result.host}})}}}});
@@ -1,80 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.preferCodePoint = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- const eslint_devkit_2 = require("@interlace/eslint-devkit");
6
- exports.preferCodePoint = (0, eslint_devkit_1.createRule)({
7
- name: 'prefer-code-point',
8
- meta: {
9
- type: 'suggestion',
10
- docs: {
11
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/prefer-code-point.md',
12
- description: 'Prefer codePointAt over charCodeAt for proper Unicode character handling',
13
- },
14
- messages: {
15
- preferCodePoint: (0, eslint_devkit_2.formatLLMMessage)({
16
- icon: eslint_devkit_2.MessageIcons.WARNING,
17
- issueName: 'Prefer codePointAt',
18
- description: 'Use codePointAt instead of charCodeAt for Unicode safety',
19
- severity: 'MEDIUM',
20
- fix: 'Replace charCodeAt() with codePointAt()',
21
- documentationLink: 'https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-code-point.md',
22
- }),
23
- },
24
- schema: [
25
- {
26
- type: 'object',
27
- properties: {},
28
- additionalProperties: false,
29
- },
30
- ],
31
- },
32
- defaultOptions: [],
33
- create(context) {
34
- function isInAllowedContext(node) {
35
- return false;
36
- }
37
- function shouldIgnoreCall(node) {
38
- if (node.callee.type === 'MemberExpression') {
39
- if (node.callee.optional) {
40
- return true;
41
- }
42
- if (node.callee.computed &&
43
- node.callee.property.type === 'Identifier') {
44
- return true;
45
- }
46
- }
47
- return false;
48
- }
49
- function isCharCodeAtCall(node) {
50
- if (node.callee.type === 'MemberExpression') {
51
- if (node.callee.property.type === 'Identifier' &&
52
- node.callee.property.name === 'charCodeAt') {
53
- return true;
54
- }
55
- if (node.callee.computed &&
56
- node.callee.property.type === 'Literal' &&
57
- node.callee.property.value === 'charCodeAt') {
58
- return true;
59
- }
60
- }
61
- return false;
62
- }
63
- return {
64
- CallExpression(node) {
65
- if (isCharCodeAtCall(node) &&
66
- !isInAllowedContext(node) &&
67
- !shouldIgnoreCall(node)) {
68
- context.report({
69
- node,
70
- messageId: 'preferCodePoint',
71
- data: {
72
- current: 'charCodeAt()',
73
- fix: 'codePointAt()',
74
- },
75
- });
76
- }
77
- },
78
- };
79
- },
80
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.preferCodePoint=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.preferCodePoint=(0,eslint_devkit_1.createRule)({name:"prefer-code-point",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/prefer-code-point.md",description:"Prefer codePointAt over charCodeAt for proper Unicode character handling"},messages:{preferCodePoint:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Prefer codePointAt",description:"Use codePointAt instead of charCodeAt for Unicode safety",severity:"MEDIUM",fix:"Replace charCodeAt() with codePointAt()",documentationLink:"https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-code-point.md"})},schema:[{type:"object",properties:{},additionalProperties:false}]},defaultOptions:[],create(context){function isInAllowedContext(node){return false}function shouldIgnoreCall(node){if(node.callee.type==="MemberExpression"){if(node.callee.optional){return true}if(node.callee.computed&&node.callee.property.type==="Identifier"){return true}}return false}function isCharCodeAtCall(node){if(node.callee.type==="MemberExpression"){if(node.callee.property.type==="Identifier"&&node.callee.property.name==="charCodeAt"){return true}if(node.callee.computed&&node.callee.property.type==="Literal"&&node.callee.property.value==="charCodeAt"){return true}}return false}return{CallExpression(node){if(isCharCodeAtCall(node)&&!isInAllowedContext(node)&&!shouldIgnoreCall(node)){context.report({node,messageId:"preferCodePoint",data:{current:"charCodeAt()",fix:"codePointAt()"}})}}}}});