@the-portland-company/shell 0.89.0 → 0.91.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/eslint/index.cjs DELETED
@@ -1,172 +0,0 @@
1
- 'use strict'
2
- // Shell primitives guard. Spec: Components — shared primitives are mandatory.
3
- const SHELL_PKG = '@the-portland-company/shell'
4
- const LOCAL_NAMES = new Set(['Button', 'Card', 'Input', 'Select', 'Textarea', 'Checkbox', 'FileInput', 'PageHeader', 'FormColumn'])
5
- const RAW_TO_PRIMITIVE = { button: 'Button', input: 'Input', select: 'Select', textarea: 'Textarea' }
6
- const GLOBAL_CLASS_RE = /\b(btn|input)(-[a-z]+)?\b/
7
- const EXEMPT_RE = /shell-primitive-exempt:\s*\S+/
8
-
9
- const isShellSource = (s) => typeof s === 'string' && (s === SHELL_PKG || s.startsWith(SHELL_PKG + '/'))
10
-
11
- /** True when an exempt comment (with a reason) is on the node's line or the line above. */
12
- function isExempt(context, node) {
13
- const sc = context.sourceCode || context.getSourceCode()
14
- const start = node.loc.start.line
15
- return sc.getAllComments().some(
16
- (c) => EXEMPT_RE.test(c.value) && c.loc.end.line >= start - 1 && c.loc.end.line <= start,
17
- )
18
- }
19
-
20
- const noLocalPrimitives = {
21
- meta: {
22
- type: 'problem',
23
- schema: [],
24
- messages: {
25
- local: 'Do not declare or export a local "{{name}}". Import it from @the-portland-company/shell (shared primitives are mandatory); add variants to the shell instead.',
26
- },
27
- },
28
- create(context) {
29
- const report = (node, name) => {
30
- if (LOCAL_NAMES.has(name) && !isExempt(context, node)) {
31
- context.report({ node, messageId: 'local', data: { name } })
32
- }
33
- }
34
- return {
35
- FunctionDeclaration: (n) => n.id && report(n.id, n.id.name),
36
- ClassDeclaration: (n) => n.id && report(n.id, n.id.name),
37
- VariableDeclarator: (n) => n.id.type === 'Identifier' && report(n.id, n.id.name),
38
- ExportSpecifier(n) {
39
- const parent = n.parent
40
- const exported = n.exported.name || n.exported.value
41
- if (parent.source) {
42
- if (!isShellSource(parent.source.value)) report(n, exported)
43
- } else if (n.local.name !== exported) {
44
- report(n, exported) // `export { Foo as Button }`; same-name locals are caught at declaration
45
- }
46
- },
47
- }
48
- },
49
- }
50
-
51
- const noRawControlStyling = {
52
- meta: {
53
- type: 'problem',
54
- schema: [],
55
- messages: {
56
- raw: '<{{tag}}> with {{prop}} hand-rolls a control. Use <{{primitive}}> from @the-portland-company/shell, or add "// shell-primitive-exempt: <reason>".',
57
- },
58
- },
59
- create(context) {
60
- const imported = new Set()
61
- return {
62
- ImportDeclaration(n) {
63
- if (!isShellSource(n.source.value)) return
64
- for (const s of n.specifiers) if (s.type === 'ImportSpecifier') imported.add(s.imported.name || s.imported.value)
65
- },
66
- JSXOpeningElement(n) {
67
- if (n.name.type !== 'JSXIdentifier') return
68
- const tag = n.name.name
69
- let primitive = RAW_TO_PRIMITIVE[tag]
70
- if (tag === 'input') {
71
- // <input type="checkbox|file"> needs Checkbox / FileInput; importing Input does not excuse it.
72
- const typeAttr = n.attributes.find(
73
- (a) => a.type === 'JSXAttribute' && a.name.type === 'JSXIdentifier' && a.name.name === 'type',
74
- )
75
- const v = typeAttr && typeAttr.value
76
- const typeValue =
77
- v && v.type === 'Literal' ? v.value
78
- : v && v.type === 'JSXExpressionContainer' && v.expression.type === 'Literal' ? v.expression.value
79
- : null
80
- if (typeValue === 'checkbox') primitive = 'Checkbox'
81
- else if (typeValue === 'file') primitive = 'FileInput'
82
- }
83
- if (!primitive) return
84
- const attr = n.attributes.find(
85
- (a) => a.type === 'JSXAttribute' && a.name.type === 'JSXIdentifier' && (a.name.name === 'className' || a.name.name === 'style'),
86
- )
87
- if (!attr) return
88
- if (imported.has(primitive)) return // wrapping the shell primitive
89
- if (isExempt(context, n)) return
90
- context.report({ node: attr, messageId: 'raw', data: { tag, prop: attr.name.name, primitive } })
91
- },
92
- }
93
- },
94
- }
95
-
96
- const noGlobalControlClasses = {
97
- meta: {
98
- type: 'problem',
99
- schema: [],
100
- messages: {
101
- global: 'className "{{value}}" relies on a global btn/input class. Use the shell Button/Input primitives.',
102
- },
103
- },
104
- create(context) {
105
- function scan(node, root) {
106
- if (!node) return
107
- switch (node.type) {
108
- case 'Literal':
109
- if (typeof node.value === 'string') check(node.value, node, root)
110
- break
111
- case 'TemplateLiteral':
112
- node.quasis.forEach((q) => check(q.value.cooked || '', q, root))
113
- node.expressions.forEach((e) => scan(e, root))
114
- break
115
- case 'JSXExpressionContainer':
116
- scan(node.expression, root)
117
- break
118
- case 'ConditionalExpression':
119
- scan(node.consequent, root)
120
- scan(node.alternate, root)
121
- break
122
- case 'LogicalExpression':
123
- case 'BinaryExpression':
124
- scan(node.left, root)
125
- scan(node.right, root)
126
- break
127
- case 'CallExpression':
128
- node.arguments.forEach((a) => scan(a, root))
129
- break
130
- case 'ArrayExpression':
131
- node.elements.forEach((a) => scan(a, root))
132
- break
133
- case 'ObjectExpression':
134
- node.properties.forEach((p) => p.type === 'Property' && scan(p.key, root))
135
- break
136
- }
137
- }
138
- function check(text, node, root) {
139
- if (GLOBAL_CLASS_RE.test(text) && !isExempt(context, root)) {
140
- context.report({ node, messageId: 'global', data: { value: text.trim() } })
141
- }
142
- }
143
- return {
144
- JSXAttribute(n) {
145
- if (n.name.type === 'JSXIdentifier' && n.name.name === 'className') scan(n.value, n)
146
- },
147
- }
148
- },
149
- }
150
-
151
- const rules = {
152
- 'no-local-primitives': noLocalPrimitives,
153
- 'no-raw-control-styling': noRawControlStyling,
154
- 'no-global-control-classes': noGlobalControlClasses,
155
- }
156
-
157
- const plugin = { meta: { name: SHELL_PKG + '/eslint' }, rules, configs: {} }
158
-
159
- // Flat config: `export default [...shell.configs.recommended]`
160
- plugin.configs.recommended = [
161
- {
162
- files: ['**/*.{js,jsx,ts,tsx,mjs,cjs}'],
163
- plugins: { 'politogy-shell': plugin },
164
- rules: {
165
- 'politogy-shell/no-local-primitives': 'error',
166
- 'politogy-shell/no-raw-control-styling': 'error',
167
- 'politogy-shell/no-global-control-classes': 'error',
168
- },
169
- },
170
- ]
171
-
172
- module.exports = plugin
package/eslint/index.d.ts DELETED
@@ -1,6 +0,0 @@
1
- import type { Linter, ESLint } from 'eslint'
2
-
3
- declare const plugin: ESLint.Plugin & { configs: { recommended: Linter.Config[] } }
4
- export default plugin
5
- export const rules: ESLint.Plugin['rules']
6
- export const configs: { recommended: Linter.Config[] }
package/eslint/index.mjs DELETED
@@ -1,5 +0,0 @@
1
- import plugin from './index.cjs'
2
-
3
- export default plugin
4
- export const rules = plugin.rules
5
- export const configs = plugin.configs