@solid-analytics/babel-plugin 1.0.0-alpha.0 → 1.0.0-beta.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/index.d.ts +2 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +248 -246
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { PluginObj } from '@babel/core';
|
|
2
2
|
import { PluginPass } from '@babel/core';
|
|
3
|
-
import * as t from '@babel/types';
|
|
4
3
|
|
|
5
4
|
declare const _default: (api: object, options: PluginOptions | null | undefined, dirname: string) => PluginObj<PluginState>;
|
|
6
5
|
export default _default;
|
|
7
6
|
|
|
8
7
|
declare interface PluginOptions {
|
|
9
|
-
|
|
8
|
+
directivesImport?: string;
|
|
10
9
|
prefix?: string;
|
|
11
10
|
debug?: boolean;
|
|
12
11
|
validate?: boolean;
|
|
@@ -16,8 +15,7 @@ declare interface PluginOptions {
|
|
|
16
15
|
|
|
17
16
|
declare interface PluginState extends PluginPass {
|
|
18
17
|
opts: PluginOptions;
|
|
19
|
-
|
|
20
|
-
runtimeIdentifier?: t.Identifier;
|
|
18
|
+
directivesImported?: Set<string>;
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
export { }
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const
|
|
1
|
+
"use strict";const h=require("@babel/helper-plugin-utils"),x=require("@babel/types");function k(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const i in t)if(i!=="default"){const s=Object.getOwnPropertyDescriptor(t,i);Object.defineProperty(e,i,s.get?s:{enumerable:!0,get:()=>t[i]})}}return e.default=t,Object.freeze(e)}const n=k(x);function w(t){const e=["click","submit","focus","blur","change","hover","scroll","visible","identify","page","track","input","keydown","keyup","keypress","mouseenter","mouseleave","mousedown","mouseup","touchstart","touchend","touchmove"];return e.includes(t)?null:{message:`Unknown analytics directive: "analytics:${t}". Valid directives: ${e.join(", ")}`}}function E(t,e){if(typeof t=="string"){if(t.trim().length===0)return{message:`Event name cannot be empty for analytics:${e}`};if(/\s/.test(t))return{message:`Event name "${t}" contains spaces. Did you mean to use underscores?`};/[A-Z]/.test(t)&&!/^[A-Z_]+$/.test(t)&&console.warn(`[babel-plugin] Event name "${t}" uses camelCase. Consider using snake_case for consistency.`)}return null}function $(t,e){if(n.isIdentifier(t)){const i=t.name;if(i==="undefined"||i==="null")return{message:`Invalid payload value "${i}" for analytics:${e}. Use undefined or omit the payload instead.`}}return n.isObjectExpression(t)&&S(t)?{message:`Duplicate keys in payload object for analytics:${e}`}:null}function S(t){const e=new Set;for(const i of t.properties)if(n.isObjectProperty(i)&&n.isIdentifier(i.key)){const s=i.key.name;if(e.has(s))return!0;e.add(s)}return!1}function P(t,e){const s={submit:["form"],change:["input","select","textarea"],input:["input","textarea"],focus:["input","textarea","select","button","a"],blur:["input","textarea","select","button","a"]}[t];return s&&!s.includes(e.toLowerCase())?{message:`analytics:${t} should only be used on ${s.join(", ")} elements, but found on <${e}>`}:null}function D(t,e){if(t==="identify"){if(n.isStringLiteral(e))return{message:"analytics:identify requires tuple syntax: [userId, traits]"};if(n.isJSXExpressionContainer(e)){const i=e.expression;if(n.isArrayExpression(i)){if(i.elements.length<1)return{message:"analytics:identify requires at least userId: [userId, traits?]"};if(i.elements.length>2)return{message:"analytics:identify takes exactly 2 arguments: [userId, traits?]"}}else return{message:"analytics:identify must be an array: [userId, traits?]"}}}return null}const I={keys:["password","passwd","pwd","ssn","social_security","social","credit","card","cvv","cvc","pin","email","mail","e_mail","phone","mobile","telephone","tel","address","street","zip","postal","zipcode","dob","birth","birthdate","birthday","ip","ip_address","ipv4","ipv6","ipaddr","token","secret","api_key","apikey","auth","name","firstname","lastname","fullname"],values:[/\d{3}-\d{2}-\d{4}/,/\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}/,/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/,/\+?\d{1,3}[- ]?\(?\d{3}\)?[- ]?\d{3}[- ]?\d{4}/,/\b\d{1,5}\s\w+\s(?:street|st|avenue|ave|road|rd|drive|dr|lane|ln|way|court|ct)\b/i,/\b\d{5}(?:-\d{4})?\b/]};function j(t){const e=[];if(n.isObjectExpression(t)){for(const i of t.properties)if(n.isObjectProperty(i)){if(n.isIdentifier(i.key)){const s=i.key.name.toLowerCase();for(const o of I.keys)if(s.includes(o)){e.push(`Potential PII detected in property "${i.key.name}". Consider anonymizing this data.`);break}}if(n.isStringLiteral(i.value)){const s=i.value.value;for(const o of I.values)if(o.test(s)){const u=n.isIdentifier(i.key)?i.key.name:"unknown";e.push(`Potential PII detected in value of "${u}". The value appears to match a sensitive pattern.`);break}}}}return e}const A=h.declare((t,e)=>{t.assertVersion(7);const i=e.directivesImport||"@solid-analytics/solid/directives",s=e.prefix||"analytics",o=e.debug||!1,u=e.validate!==!1,a=e.warnPII!==!1;return{name:"@solid-analytics/babel-plugin",visitor:{Program:{enter(r,d){d.directivesImported=new Set},exit(r,d){if(d.directivesImported&&d.directivesImported.size>0){const p=Array.from(d.directivesImported);let y=null;r.traverse({ImportDeclaration(c){c.node.source.value===i&&(y=c)}}),y&&p.forEach(c=>{const f=n.expressionStatement(n.unaryExpression("void",n.identifier(c),!0));y.insertAfter(f)})}}},JSXElement(r,d){const p=r.node.openingElement,y=p.attributes,c=n.isJSXIdentifier(p.name)?p.name.name:"Unknown",f=[];if(y.forEach(l=>{if(n.isJSXAttribute(l)&&n.isJSXNamespacedName(l.name)){const m=l.name.namespace.name,b=l.name.name.name;m===s&&f.push({attr:l,directive:b})}}),f.length!==0)for(const{attr:l,directive:m}of f){if(o&&console.log(`[babel-plugin] Found directive: ${s}:${m}`),u){const b=w(m);if(b)throw r.buildCodeFrameError(b.message);const g=P(m,c);g&&console.warn(`[babel-plugin] ${g.message}`);const v=D(m,l.value);if(v)throw r.buildCodeFrameError(v.message)}C(r,p,l,m,d,i,{debug:o,validate:u,warnPII:a})}}}}});function _(t,e,i,s){if(e.directivesImported?.has(s))return;const o=t.findParent(a=>a.isProgram());if(!o)return;let u=null;if(o.traverse({ImportDeclaration(a){a.node.source.value===i&&(u=a)}}),u){if(!u.node.specifiers.some(r=>n.isImportSpecifier(r)&&n.isIdentifier(r.imported)&&r.imported.name===s)){const r=n.importSpecifier(n.identifier(s),n.identifier(s));u.node.specifiers.push(r)}}else{const a=n.importDeclaration([n.importSpecifier(n.identifier(s),n.identifier(s))],n.stringLiteral(i));o.unshiftContainer("body",a)}e.directivesImported?.add(s)}function C(t,e,i,s,o,u,a){const r=i.value;if(n.isStringLiteral(r)&&a.validate){const c=E(r.value,s);c&&console.warn(`[babel-plugin] ${c.message}`)}if(n.isJSXExpressionContainer(r)){const c=r.expression;if(n.isArrayExpression(c)){const[,f]=c.elements;if(f&&n.isExpression(f)){if(a.validate){const l=$(f,s);l&&console.warn(`[babel-plugin] ${l.message}`)}a.warnPII&&j(f).forEach(m=>{console.warn(`[babel-plugin] ${m}`)})}}}const d=`analytics${s.charAt(0).toUpperCase()}${s.slice(1)}`;_(t,o,u,d);const p=e.attributes.indexOf(i);p!==-1&&e.attributes.splice(p,1);const y=n.jsxAttribute(n.jsxNamespacedName(n.jsxIdentifier("use"),n.jsxIdentifier(d)),r);e.attributes.push(y),a.debug&&console.log(`[babel-plugin] Transformed analytics:${s} -> use:${d}`)}module.exports=A;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/validation.ts","../src/index.ts"],"sourcesContent":["/**\n * Validation utilities for analytics directives\n */\n\nimport * as t from \"@babel/types\";\n\nexport interface ValidationError {\n message: string;\n loc?: {\n line: number;\n column: number;\n };\n}\n\n/**\n * Validate directive name\n */\nexport function validateDirective(directive: string): ValidationError | null {\n const validDirectives = [\n \"click\",\n \"submit\",\n \"focus\",\n \"blur\",\n \"change\",\n \"hover\",\n \"scroll\",\n \"visible\",\n \"identify\",\n \"page\",\n \"track\",\n \"input\",\n \"keydown\",\n \"keyup\",\n \"keypress\",\n \"mouseenter\",\n \"mouseleave\",\n \"mousedown\",\n \"mouseup\",\n \"touchstart\",\n \"touchend\",\n \"touchmove\",\n ];\n\n if (!validDirectives.includes(directive)) {\n return {\n message: `Unknown analytics directive: \"analytics:${directive}\". Valid directives: ${validDirectives.join(\n \", \"\n )}`,\n };\n }\n\n return null;\n}\n\n/**\n * Validate event name\n */\nexport function validateEventName(\n eventName: string | t.Expression,\n directive: string\n): ValidationError | null {\n if (typeof eventName === \"string\") {\n // String event names\n if (eventName.trim().length === 0) {\n return {\n message: `Event name cannot be empty for analytics:${directive}`,\n };\n }\n\n // Warn about potential typos (spaces, special characters)\n if (/\\s/.test(eventName)) {\n return {\n message: `Event name \"${eventName}\" contains spaces. Did you mean to use underscores?`,\n };\n }\n\n // Recommend snake_case\n if (/[A-Z]/.test(eventName) && !/^[A-Z_]+$/.test(eventName)) {\n // Has mixed case but not all caps (which might be intentional)\n console.warn(\n `[babel-plugin] Event name \"${eventName}\" uses camelCase. Consider using snake_case for consistency.`\n );\n }\n }\n\n return null;\n}\n\n/**\n * Validate payload structure\n */\nexport function validatePayload(\n payload: t.Expression,\n directive: string\n): ValidationError | null {\n // Check for common mistakes\n if (t.isIdentifier(payload)) {\n const name = payload.name;\n if (name === \"undefined\" || name === \"null\") {\n return {\n message: `Invalid payload value \"${name}\" for analytics:${directive}. Use undefined or omit the payload instead.`,\n };\n }\n }\n\n // Validate object expression structure\n if (t.isObjectExpression(payload)) {\n const hasDuplicateKeys = checkDuplicateKeys(payload);\n if (hasDuplicateKeys) {\n return {\n message: `Duplicate keys in payload object for analytics:${directive}`,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Check for duplicate keys in object expression\n */\nfunction checkDuplicateKeys(obj: t.ObjectExpression): boolean {\n const keys = new Set<string>();\n\n for (const prop of obj.properties) {\n if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {\n const key = prop.key.name;\n if (keys.has(key)) {\n return true;\n }\n keys.add(key);\n }\n }\n\n return false;\n}\n\n/**\n * Validate directive is used on appropriate element\n */\nexport function validateDirectiveOnElement(\n directive: string,\n elementName: string\n): ValidationError | null {\n const restrictions: Record<string, string[]> = {\n submit: [\"form\"],\n change: [\"input\", \"select\", \"textarea\"],\n input: [\"input\", \"textarea\"],\n focus: [\"input\", \"textarea\", \"select\", \"button\", \"a\"],\n blur: [\"input\", \"textarea\", \"select\", \"button\", \"a\"],\n };\n\n const allowedElements = restrictions[directive];\n if (allowedElements && !allowedElements.includes(elementName.toLowerCase())) {\n return {\n message: `analytics:${directive} should only be used on ${allowedElements.join(\n \", \"\n )} elements, but found on <${elementName}>`,\n };\n }\n\n return null;\n}\n\n/**\n * Validate special directives (identify, page)\n */\nexport function validateSpecialDirective(\n directive: string,\n value: t.JSXAttribute[\"value\"]\n): ValidationError | null {\n if (directive === \"identify\") {\n // identify must use tuple syntax: [userId, traits]\n if (t.isStringLiteral(value)) {\n return {\n message: \"analytics:identify requires tuple syntax: [userId, traits]\",\n };\n }\n\n if (t.isJSXExpressionContainer(value)) {\n const expr = value.expression;\n if (t.isArrayExpression(expr)) {\n if (expr.elements.length < 1) {\n return {\n message:\n \"analytics:identify requires at least userId: [userId, traits?]\",\n };\n }\n if (expr.elements.length > 2) {\n return {\n message:\n \"analytics:identify takes exactly 2 arguments: [userId, traits?]\",\n };\n }\n } else {\n return {\n message: \"analytics:identify must be an array: [userId, traits?]\",\n };\n }\n }\n }\n\n return null;\n}\n\n/**\n * Sanitize event names (prevent injection)\n */\nexport function sanitizeEventName(eventName: string): string {\n // Remove any potentially dangerous characters\n return eventName\n .replace(/[^\\w\\s\\-_.]/g, \"\") // Only allow word chars, spaces, hyphens, underscores, dots\n .trim();\n}\n\n/**\n * Check for PII in payload (warning only)\n */\nexport function checkForPII(payload: t.Expression): string[] {\n const warnings: string[] = [];\n\n if (t.isObjectExpression(payload)) {\n const piiKeys = [\"password\", \"ssn\", \"credit\", \"card\", \"cvv\", \"pin\"];\n\n for (const prop of payload.properties) {\n if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {\n const key = prop.key.name.toLowerCase();\n\n if (piiKeys.some((piiKey) => key.includes(piiKey))) {\n warnings.push(\n `Potential PII detected in payload key \"${prop.key.name}\". Consider anonymizing this data.`\n );\n }\n }\n }\n }\n\n return warnings;\n}\n","/**\n * @solid-analytics/babel-plugin (Enhanced with Validation)\n *\n * Transforms analytics directives into event handlers with build-time validation\n */\n\nimport { declare } from \"@babel/helper-plugin-utils\";\nimport type { PluginObj, PluginPass } from \"@babel/core\";\nimport * as t from \"@babel/types\";\nimport {\n validateDirective,\n validateEventName,\n validatePayload,\n validateDirectiveOnElement,\n validateSpecialDirective,\n sanitizeEventName,\n checkForPII,\n} from \"./validation\";\n\n// Directive to event handler mapping (expanded in Phase 2)\nconst DIRECTIVE_TO_EVENT_MAP: Record<string, string> = {\n click: \"onClick\",\n submit: \"onSubmit\",\n focus: \"onFocus\",\n blur: \"onBlur\",\n change: \"onChange\",\n hover: \"onMouseEnter\",\n scroll: \"onScroll\",\n input: \"onInput\",\n keydown: \"onKeyDown\",\n keyup: \"onKeyUp\",\n keypress: \"onKeyPress\",\n mouseenter: \"onMouseEnter\",\n mouseleave: \"onMouseLeave\",\n mousedown: \"onMouseDown\",\n mouseup: \"onMouseUp\",\n touchstart: \"onTouchStart\",\n touchend: \"onTouchEnd\",\n touchmove: \"onTouchMove\",\n};\n\nconst SPECIAL_DIRECTIVES = [\"visible\", \"identify\", \"page\", \"track\"];\n\ninterface PluginOptions {\n runtimeImport?: string;\n prefix?: string;\n debug?: boolean;\n validate?: boolean;\n sanitize?: boolean;\n warnPII?: boolean;\n}\n\ninterface PluginState extends PluginPass {\n opts: PluginOptions;\n runtimeImportAdded?: boolean;\n runtimeIdentifier?: t.Identifier;\n}\n\nexport default declare<PluginOptions, PluginObj<PluginState>>(\n (api, options) => {\n api.assertVersion(7);\n\n const runtimeImport = options.runtimeImport || \"@solid-analytics/runtime\";\n const prefix = options.prefix || \"analytics\";\n const debug = options.debug || false;\n const validate = options.validate !== false;\n const sanitize = options.sanitize !== false;\n const warnPII = options.warnPII !== false;\n\n return {\n name: \"@solid-analytics/babel-plugin\",\n\n visitor: {\n Program: {\n enter(_path, state) {\n state.runtimeImportAdded = false;\n state.runtimeIdentifier = undefined;\n },\n },\n\n JSXElement(path, state) {\n const openingElement = path.node.openingElement;\n const attributes = openingElement.attributes;\n\n // Get element name for validation\n const elementName = t.isJSXIdentifier(openingElement.name)\n ? openingElement.name.name\n : \"Unknown\";\n\n // Find analytics directives\n const analyticsAttrs: Array<{\n attr: t.JSXAttribute;\n directive: string;\n }> = [];\n\n attributes.forEach((attr) => {\n if (t.isJSXAttribute(attr) && t.isJSXNamespacedName(attr.name)) {\n const namespace = attr.name.namespace.name;\n const name = attr.name.name.name;\n\n if (namespace === prefix) {\n analyticsAttrs.push({\n attr,\n directive: name,\n });\n }\n }\n });\n\n if (analyticsAttrs.length === 0) return;\n\n // Ensure runtime import is added\n if (!state.runtimeImportAdded) {\n addRuntimeImport(path, state, runtimeImport);\n }\n\n // Transform each analytics directive\n for (const { attr, directive } of analyticsAttrs) {\n if (debug) {\n console.log(\n `[babel-plugin] Found directive: ${prefix}:${directive}`\n );\n }\n\n // Validate directive\n if (validate) {\n const directiveError = validateDirective(directive);\n if (directiveError) {\n throw path.buildCodeFrameError(directiveError.message);\n }\n\n const elementError = validateDirectiveOnElement(\n directive,\n elementName\n );\n if (elementError) {\n console.warn(`[babel-plugin] ${elementError.message}`);\n }\n\n const specialError = validateSpecialDirective(\n directive,\n attr.value\n );\n if (specialError) {\n throw path.buildCodeFrameError(specialError.message);\n }\n }\n\n transformDirective(openingElement, attr, directive, state, {\n debug,\n validate,\n sanitize,\n warnPII,\n });\n }\n },\n },\n };\n }\n);\n\nfunction addRuntimeImport(\n path: any,\n state: PluginState,\n runtimeImport: string\n) {\n const program = path.findParent((p: any) => p.isProgram());\n if (!program) return;\n\n const importDeclaration = t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"__analytics\"),\n t.identifier(\"__analytics\")\n ),\n ],\n t.stringLiteral(runtimeImport)\n );\n\n program.unshiftContainer(\"body\", importDeclaration);\n state.runtimeImportAdded = true;\n state.runtimeIdentifier = t.identifier(\"__analytics\");\n}\n\nfunction transformDirective(\n openingElement: t.JSXOpeningElement,\n attr: t.JSXAttribute,\n directive: string,\n state: PluginState,\n options: {\n debug: boolean;\n validate: boolean;\n sanitize: boolean;\n warnPII: boolean;\n }\n) {\n const value = attr.value;\n let eventName: string | t.Expression;\n let payload: t.Expression | null = null;\n\n if (t.isStringLiteral(value)) {\n eventName = value.value;\n\n // Validate event name\n if (options.validate) {\n const nameError = validateEventName(eventName, directive);\n if (nameError) {\n console.warn(`[babel-plugin] ${nameError.message}`);\n }\n }\n\n // Sanitize event name\n if (options.sanitize && typeof eventName === \"string\") {\n eventName = sanitizeEventName(eventName);\n }\n } else if (t.isJSXExpressionContainer(value)) {\n const expr = value.expression;\n\n if (t.isArrayExpression(expr)) {\n const [eventExpr, payloadExpr] = expr.elements;\n\n if (eventExpr && t.isStringLiteral(eventExpr)) {\n eventName = eventExpr.value;\n\n if (options.sanitize) {\n eventName = sanitizeEventName(eventName);\n }\n } else if (eventExpr && t.isExpression(eventExpr)) {\n eventName = eventExpr;\n } else {\n throw new Error(`Invalid event name in analytics:${directive}`);\n }\n\n if (payloadExpr && t.isExpression(payloadExpr)) {\n payload = payloadExpr;\n\n // Validate payload\n if (options.validate) {\n const payloadError = validatePayload(payload, directive);\n if (payloadError) {\n console.warn(`[babel-plugin] ${payloadError.message}`);\n }\n }\n\n // Check for PII\n if (options.warnPII) {\n const piiWarnings = checkForPII(payload);\n piiWarnings.forEach((warning) => {\n console.warn(`[babel-plugin] ${warning}`);\n });\n }\n }\n } else if (t.isStringLiteral(expr)) {\n eventName = expr.value;\n if (options.sanitize) {\n eventName = sanitizeEventName(eventName);\n }\n } else if (t.isExpression(expr)) {\n eventName = expr;\n } else {\n throw new Error(`Invalid value for analytics:${directive}`);\n }\n } else {\n throw new Error(`Invalid value for analytics:${directive}`);\n }\n\n // Remove the analytics directive attribute by finding it\n const attrIndex = openingElement.attributes.indexOf(attr);\n if (attrIndex !== -1) {\n openingElement.attributes.splice(attrIndex, 1);\n }\n\n // Handle special directives\n if (SPECIAL_DIRECTIVES.includes(directive)) {\n handleSpecialDirective(\n openingElement,\n directive,\n eventName,\n payload,\n state,\n options.debug\n );\n return;\n }\n\n // Get the corresponding DOM event\n const domEvent = DIRECTIVE_TO_EVENT_MAP[directive];\n if (!domEvent && options.validate) {\n throw new Error(`Unknown analytics directive: ${directive}`);\n }\n\n // If validation is disabled and we don't know this directive, skip it\n if (!domEvent) {\n return;\n }\n\n // Create the event handler\n const handler = createEventHandler(\n directive,\n eventName,\n payload,\n state,\n options.debug\n );\n\n // Add the event handler as a JSX attribute\n const eventAttr = t.jsxAttribute(\n t.jsxIdentifier(domEvent),\n t.jsxExpressionContainer(handler)\n );\n\n openingElement.attributes.push(eventAttr);\n\n if (options.debug) {\n console.log(`[babel-plugin] Transformed ${directive} -> ${domEvent}`);\n }\n}\n\nfunction createEventHandler(\n _directive: string,\n eventName: string | t.Expression,\n payload: t.Expression | null,\n state: PluginState,\n _debug: boolean\n): t.ArrowFunctionExpression {\n const runtimeId = state.runtimeIdentifier || t.identifier(\"__analytics\");\n\n const trackCall = t.callExpression(\n t.memberExpression(runtimeId, t.identifier(\"track\")),\n [\n typeof eventName === \"string\" ? t.stringLiteral(eventName) : eventName,\n payload || t.identifier(\"undefined\"),\n t.identifier(\"e\"),\n ]\n );\n\n return t.arrowFunctionExpression([t.identifier(\"e\")], trackCall);\n}\n\nfunction handleSpecialDirective(\n openingElement: t.JSXOpeningElement,\n directive: string,\n eventName: string | t.Expression,\n payload: t.Expression | null,\n state: PluginState,\n debug: boolean\n) {\n switch (directive) {\n case \"visible\":\n const visibleAttr = t.jsxAttribute(\n t.jsxIdentifier(\"data-analytics-visible\"),\n t.stringLiteral(\n typeof eventName === \"string\"\n ? eventName\n : JSON.stringify({ dynamic: true })\n )\n );\n openingElement.attributes.push(visibleAttr);\n\n if (payload) {\n const payloadAttr = t.jsxAttribute(\n t.jsxIdentifier(\"data-analytics-visible-payload\"),\n t.jsxExpressionContainer(payload)\n );\n openingElement.attributes.push(payloadAttr);\n }\n break;\n\n case \"identify\":\n const identifyAttr = t.jsxAttribute(\n t.jsxIdentifier(\"data-analytics-identify\"),\n t.jsxExpressionContainer(\n t.arrayExpression([\n typeof eventName === \"string\"\n ? t.stringLiteral(eventName)\n : eventName,\n payload || t.identifier(\"undefined\"),\n ])\n )\n );\n openingElement.attributes.push(identifyAttr);\n break;\n\n case \"page\":\n const pageAttr = t.jsxAttribute(\n t.jsxIdentifier(\"data-analytics-page\"),\n t.jsxExpressionContainer(\n t.arrayExpression([\n typeof eventName === \"string\"\n ? t.stringLiteral(eventName)\n : eventName,\n payload || t.identifier(\"undefined\"),\n ])\n )\n );\n openingElement.attributes.push(pageAttr);\n break;\n\n case \"track\":\n const trackHandler = createEventHandler(\n \"track\",\n eventName,\n payload,\n state,\n debug\n );\n\n const trackAttr = t.jsxAttribute(\n t.jsxIdentifier(\"onClick\"),\n t.jsxExpressionContainer(trackHandler)\n );\n openingElement.attributes.push(trackAttr);\n break;\n }\n\n if (debug) {\n console.log(`[babel-plugin] Handled special directive: ${directive}`);\n }\n}\n"],"names":["validateDirective","directive","validDirectives","validateEventName","eventName","validatePayload","payload","t","name","checkDuplicateKeys","obj","keys","prop","key","validateDirectiveOnElement","elementName","allowedElements","validateSpecialDirective","value","expr","sanitizeEventName","checkForPII","warnings","piiKeys","piiKey","DIRECTIVE_TO_EVENT_MAP","SPECIAL_DIRECTIVES","index","declare","api","options","runtimeImport","prefix","debug","validate","sanitize","warnPII","_path","state","path","openingElement","attributes","analyticsAttrs","attr","namespace","addRuntimeImport","directiveError","elementError","specialError","transformDirective","program","p","importDeclaration","nameError","eventExpr","payloadExpr","payloadError","warning","attrIndex","handleSpecialDirective","domEvent","handler","createEventHandler","eventAttr","_directive","_debug","runtimeId","trackCall","visibleAttr","payloadAttr","identifyAttr","pageAttr","trackHandler","trackAttr"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/validation.ts","../src/index.ts"],"sourcesContent":["/**\n * Validation utilities for analytics directives\n */\n\nimport * as t from \"@babel/types\";\n\nexport interface ValidationError {\n message: string;\n loc?: {\n line: number;\n column: number;\n };\n}\n\n/**\n * Validate directive name\n */\nexport function validateDirective(directive: string): ValidationError | null {\n const validDirectives = [\n \"click\",\n \"submit\",\n \"focus\",\n \"blur\",\n \"change\",\n \"hover\",\n \"scroll\",\n \"visible\",\n \"identify\",\n \"page\",\n \"track\",\n \"input\",\n \"keydown\",\n \"keyup\",\n \"keypress\",\n \"mouseenter\",\n \"mouseleave\",\n \"mousedown\",\n \"mouseup\",\n \"touchstart\",\n \"touchend\",\n \"touchmove\",\n ];\n\n if (!validDirectives.includes(directive)) {\n return {\n message: `Unknown analytics directive: \"analytics:${directive}\". Valid directives: ${validDirectives.join(\n \", \"\n )}`,\n };\n }\n\n return null;\n}\n\n/**\n * Validate event name\n */\nexport function validateEventName(\n eventName: string | t.Expression,\n directive: string\n): ValidationError | null {\n if (typeof eventName === \"string\") {\n // String event names\n if (eventName.trim().length === 0) {\n return {\n message: `Event name cannot be empty for analytics:${directive}`,\n };\n }\n\n // Warn about potential typos (spaces, special characters)\n if (/\\s/.test(eventName)) {\n return {\n message: `Event name \"${eventName}\" contains spaces. Did you mean to use underscores?`,\n };\n }\n\n // Recommend snake_case\n if (/[A-Z]/.test(eventName) && !/^[A-Z_]+$/.test(eventName)) {\n // Has mixed case but not all caps (which might be intentional)\n console.warn(\n `[babel-plugin] Event name \"${eventName}\" uses camelCase. Consider using snake_case for consistency.`\n );\n }\n }\n\n return null;\n}\n\n/**\n * Validate payload structure\n */\nexport function validatePayload(\n payload: t.Expression,\n directive: string\n): ValidationError | null {\n // Check for common mistakes\n if (t.isIdentifier(payload)) {\n const name = payload.name;\n if (name === \"undefined\" || name === \"null\") {\n return {\n message: `Invalid payload value \"${name}\" for analytics:${directive}. Use undefined or omit the payload instead.`,\n };\n }\n }\n\n // Validate object expression structure\n if (t.isObjectExpression(payload)) {\n const hasDuplicateKeys = checkDuplicateKeys(payload);\n if (hasDuplicateKeys) {\n return {\n message: `Duplicate keys in payload object for analytics:${directive}`,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Check for duplicate keys in object expression\n */\nfunction checkDuplicateKeys(obj: t.ObjectExpression): boolean {\n const keys = new Set<string>();\n\n for (const prop of obj.properties) {\n if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {\n const key = prop.key.name;\n if (keys.has(key)) {\n return true;\n }\n keys.add(key);\n }\n }\n\n return false;\n}\n\n/**\n * Validate directive is used on appropriate element\n */\nexport function validateDirectiveOnElement(\n directive: string,\n elementName: string\n): ValidationError | null {\n const restrictions: Record<string, string[]> = {\n submit: [\"form\"],\n change: [\"input\", \"select\", \"textarea\"],\n input: [\"input\", \"textarea\"],\n focus: [\"input\", \"textarea\", \"select\", \"button\", \"a\"],\n blur: [\"input\", \"textarea\", \"select\", \"button\", \"a\"],\n };\n\n const allowedElements = restrictions[directive];\n if (allowedElements && !allowedElements.includes(elementName.toLowerCase())) {\n return {\n message: `analytics:${directive} should only be used on ${allowedElements.join(\n \", \"\n )} elements, but found on <${elementName}>`,\n };\n }\n\n return null;\n}\n\n/**\n * Validate special directives (identify, page)\n */\nexport function validateSpecialDirective(\n directive: string,\n value: t.JSXAttribute[\"value\"]\n): ValidationError | null {\n if (directive === \"identify\") {\n // identify must use tuple syntax: [userId, traits]\n if (t.isStringLiteral(value)) {\n return {\n message: \"analytics:identify requires tuple syntax: [userId, traits]\",\n };\n }\n\n if (t.isJSXExpressionContainer(value)) {\n const expr = value.expression;\n if (t.isArrayExpression(expr)) {\n if (expr.elements.length < 1) {\n return {\n message:\n \"analytics:identify requires at least userId: [userId, traits?]\",\n };\n }\n if (expr.elements.length > 2) {\n return {\n message:\n \"analytics:identify takes exactly 2 arguments: [userId, traits?]\",\n };\n }\n } else {\n return {\n message: \"analytics:identify must be an array: [userId, traits?]\",\n };\n }\n }\n }\n\n return null;\n}\n\n/**\n * Sanitize event names (prevent injection)\n */\nexport function sanitizeEventName(eventName: string): string {\n // Remove any potentially dangerous characters\n return eventName\n .replace(/[^\\w\\s\\-_.]/g, \"\") // Only allow word chars, spaces, hyphens, underscores, dots\n .trim();\n}\n\n/**\n * PII detection patterns\n */\nconst PII_PATTERNS = {\n // Property names (case-insensitive)\n keys: [\n \"password\",\n \"passwd\",\n \"pwd\",\n \"ssn\",\n \"social_security\",\n \"social\",\n \"credit\",\n \"card\",\n \"cvv\",\n \"cvc\",\n \"pin\",\n \"email\",\n \"mail\",\n \"e_mail\",\n \"phone\",\n \"mobile\",\n \"telephone\",\n \"tel\",\n \"address\",\n \"street\",\n \"zip\",\n \"postal\",\n \"zipcode\",\n \"dob\",\n \"birth\",\n \"birthdate\",\n \"birthday\",\n \"ip\",\n \"ip_address\",\n \"ipv4\",\n \"ipv6\",\n \"ipaddr\",\n \"token\",\n \"secret\",\n \"api_key\",\n \"apikey\",\n \"auth\",\n \"name\",\n \"firstname\",\n \"lastname\",\n \"fullname\", // Can be PII depending on context\n ],\n\n // Value patterns (regex) - for string literals only\n values: [\n /\\d{3}-\\d{2}-\\d{4}/, // SSN (###-##-####)\n /\\d{4}[- ]?\\d{4}[- ]?\\d{4}[- ]?\\d{4}/, // Credit card\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/, // Email\n /\\+?\\d{1,3}[- ]?\\(?\\d{3}\\)?[- ]?\\d{3}[- ]?\\d{4}/, // Phone (various formats)\n /\\b\\d{1,5}\\s\\w+\\s(?:street|st|avenue|ave|road|rd|drive|dr|lane|ln|way|court|ct)\\b/i, // Street address\n /\\b\\d{5}(?:-\\d{4})?\\b/, // ZIP code\n ],\n} as const;\n\n/**\n * Check for PII in payload (warning only)\n * Enhanced with comprehensive pattern matching\n */\nexport function checkForPII(payload: t.Expression): string[] {\n const warnings: string[] = [];\n\n if (t.isObjectExpression(payload)) {\n for (const prop of payload.properties) {\n if (t.isObjectProperty(prop)) {\n // Check property key names\n if (t.isIdentifier(prop.key)) {\n const key = prop.key.name.toLowerCase();\n\n for (const piiKey of PII_PATTERNS.keys) {\n if (key.includes(piiKey)) {\n warnings.push(\n `Potential PII detected in property \"${prop.key.name}\". Consider anonymizing this data.`\n );\n break; // Only warn once per property\n }\n }\n }\n\n // Check string literal values for PII patterns\n if (t.isStringLiteral(prop.value)) {\n const value = prop.value.value;\n\n for (const pattern of PII_PATTERNS.values) {\n if (pattern.test(value)) {\n const keyName = t.isIdentifier(prop.key)\n ? prop.key.name\n : \"unknown\";\n warnings.push(\n `Potential PII detected in value of \"${keyName}\". The value appears to match a sensitive pattern.`\n );\n break; // Only warn once per value\n }\n }\n }\n }\n }\n }\n\n return warnings;\n}\n","/**\n * @solid-analytics/babel-plugin (Enhanced with Validation)\n *\n * Transforms analytics directives into event handlers with build-time validation\n */\n\nimport { declare } from \"@babel/helper-plugin-utils\";\nimport type { PluginObj, PluginPass } from \"@babel/core\";\nimport * as t from \"@babel/types\";\nimport {\n validateDirective,\n validateEventName,\n validatePayload,\n validateDirectiveOnElement,\n validateSpecialDirective,\n checkForPII,\n} from \"./validation\";\n\ninterface PluginOptions {\n directivesImport?: string;\n prefix?: string;\n debug?: boolean;\n validate?: boolean;\n sanitize?: boolean;\n warnPII?: boolean;\n}\n\ninterface PluginState extends PluginPass {\n opts: PluginOptions;\n directivesImported?: Set<string>;\n}\n\nexport default declare<PluginOptions, PluginObj<PluginState>>(\n (api, options) => {\n api.assertVersion(7);\n\n const directivesImport =\n options.directivesImport || \"@solid-analytics/solid/directives\";\n const prefix = options.prefix || \"analytics\";\n const debug = options.debug || false;\n const validate = options.validate !== false;\n const warnPII = options.warnPII !== false;\n\n return {\n name: \"@solid-analytics/babel-plugin\",\n\n visitor: {\n Program: {\n enter(_path, state) {\n state.directivesImported = new Set();\n },\n exit(path, state) {\n // After processing all JSX, add void references for all imported directives\n // to prevent tree-shaking\n if (state.directivesImported && state.directivesImported.size > 0) {\n const directives = Array.from(state.directivesImported);\n\n // Find the directives import\n let directivesImportPath: any = null;\n path.traverse({\n ImportDeclaration(importPath: any) {\n if (importPath.node.source.value === directivesImport) {\n directivesImportPath = importPath;\n }\n },\n });\n\n if (directivesImportPath) {\n // Add void statements for all directives after the import\n directives.forEach((directiveName) => {\n const voidExpression = t.expressionStatement(\n t.unaryExpression(\"void\", t.identifier(directiveName), true)\n );\n directivesImportPath.insertAfter(voidExpression);\n });\n }\n }\n },\n },\n\n JSXElement(path, state) {\n const openingElement = path.node.openingElement;\n const attributes = openingElement.attributes;\n\n // Get element name for validation\n const elementName = t.isJSXIdentifier(openingElement.name)\n ? openingElement.name.name\n : \"Unknown\";\n\n // Find analytics directives\n const analyticsAttrs: Array<{\n attr: t.JSXAttribute;\n directive: string;\n }> = [];\n\n attributes.forEach((attr) => {\n if (t.isJSXAttribute(attr) && t.isJSXNamespacedName(attr.name)) {\n const namespace = attr.name.namespace.name;\n const name = attr.name.name.name;\n\n if (namespace === prefix) {\n analyticsAttrs.push({\n attr,\n directive: name,\n });\n }\n }\n });\n\n if (analyticsAttrs.length === 0) return;\n\n // Transform each analytics directive\n for (const { attr, directive } of analyticsAttrs) {\n if (debug) {\n console.log(\n `[babel-plugin] Found directive: ${prefix}:${directive}`\n );\n }\n\n // Validate directive\n if (validate) {\n const directiveError = validateDirective(directive);\n if (directiveError) {\n throw path.buildCodeFrameError(directiveError.message);\n }\n\n const elementError = validateDirectiveOnElement(\n directive,\n elementName\n );\n if (elementError) {\n console.warn(`[babel-plugin] ${elementError.message}`);\n }\n\n const specialError = validateSpecialDirective(\n directive,\n attr.value\n );\n if (specialError) {\n throw path.buildCodeFrameError(specialError.message);\n }\n }\n\n transformDirective(\n path,\n openingElement,\n attr,\n directive,\n state,\n directivesImport,\n {\n debug,\n validate,\n warnPII,\n }\n );\n }\n },\n },\n };\n }\n);\n\nfunction addDirectiveImport(\n path: any,\n state: PluginState,\n directivesImport: string,\n directiveName: string\n) {\n // Check if we've already imported this directive\n if (state.directivesImported?.has(directiveName)) {\n return;\n }\n\n const program = path.findParent((p: any) => p.isProgram());\n if (!program) return;\n\n // Check if there's already an import from directivesImport\n let existingImport: any = null;\n program.traverse({\n ImportDeclaration(importPath: any) {\n if (importPath.node.source.value === directivesImport) {\n existingImport = importPath;\n }\n },\n });\n\n if (existingImport) {\n // Check if this specific directive is already imported\n const alreadyImported = existingImport.node.specifiers.some(\n (spec: any) =>\n t.isImportSpecifier(spec) &&\n t.isIdentifier(spec.imported) &&\n spec.imported.name === directiveName\n );\n\n if (!alreadyImported) {\n // Add to existing import\n const newSpecifier = t.importSpecifier(\n t.identifier(directiveName),\n t.identifier(directiveName)\n );\n existingImport.node.specifiers.push(newSpecifier);\n }\n } else {\n // Create new import\n const importDeclaration = t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(directiveName),\n t.identifier(directiveName)\n ),\n ],\n t.stringLiteral(directivesImport)\n );\n program.unshiftContainer(\"body\", importDeclaration);\n }\n\n state.directivesImported?.add(directiveName);\n}\n\nfunction transformDirective(\n path: any,\n openingElement: t.JSXOpeningElement,\n attr: t.JSXAttribute,\n directive: string,\n state: PluginState,\n directivesImport: string,\n options: {\n debug: boolean;\n validate: boolean;\n warnPII: boolean;\n }\n) {\n const value = attr.value;\n\n // Validate and sanitize if string value\n if (t.isStringLiteral(value) && options.validate) {\n const nameError = validateEventName(value.value, directive);\n if (nameError) {\n console.warn(`[babel-plugin] ${nameError.message}`);\n }\n }\n\n // Validate payload if array syntax\n if (t.isJSXExpressionContainer(value)) {\n const expr = value.expression;\n if (t.isArrayExpression(expr)) {\n const [, payloadExpr] = expr.elements;\n\n if (payloadExpr && t.isExpression(payloadExpr)) {\n if (options.validate) {\n const payloadError = validatePayload(payloadExpr, directive);\n if (payloadError) {\n console.warn(`[babel-plugin] ${payloadError.message}`);\n }\n }\n\n if (options.warnPII) {\n const piiWarnings = checkForPII(payloadExpr);\n piiWarnings.forEach((warning) => {\n console.warn(`[babel-plugin] ${warning}`);\n });\n }\n }\n }\n }\n\n // Ensure directive function is imported\n const directiveFunctionName = `analytics${directive\n .charAt(0)\n .toUpperCase()}${directive.slice(1)}`;\n addDirectiveImport(path, state, directivesImport, directiveFunctionName);\n\n // Remove the analytics:* attribute\n const attrIndex = openingElement.attributes.indexOf(attr);\n if (attrIndex !== -1) {\n openingElement.attributes.splice(attrIndex, 1);\n }\n\n // Add the use:* directive\n const directiveAttr = t.jsxAttribute(\n t.jsxNamespacedName(\n t.jsxIdentifier(\"use\"),\n t.jsxIdentifier(directiveFunctionName)\n ),\n value\n );\n\n openingElement.attributes.push(directiveAttr);\n\n if (options.debug) {\n console.log(\n `[babel-plugin] Transformed analytics:${directive} -> use:${directiveFunctionName}`\n );\n }\n}\n"],"names":["validateDirective","directive","validDirectives","validateEventName","eventName","validatePayload","payload","t","name","checkDuplicateKeys","obj","keys","prop","key","validateDirectiveOnElement","elementName","allowedElements","validateSpecialDirective","value","expr","PII_PATTERNS","checkForPII","warnings","piiKey","pattern","keyName","index","declare","api","options","directivesImport","prefix","debug","validate","warnPII","_path","state","path","directives","directivesImportPath","importPath","directiveName","voidExpression","openingElement","attributes","analyticsAttrs","attr","namespace","directiveError","elementError","specialError","transformDirective","addDirectiveImport","program","p","existingImport","spec","newSpecifier","importDeclaration","nameError","payloadExpr","payloadError","warning","directiveFunctionName","attrIndex","directiveAttr"],"mappings":"+WAiBO,SAASA,EAAkBC,EAA2C,CAC3E,MAAMC,EAAkB,CACtB,QACA,SACA,QACA,OACA,SACA,QACA,SACA,UACA,WACA,OACA,QACA,QACA,UACA,QACA,WACA,aACA,aACA,YACA,UACA,aACA,WACA,WAAA,EAGF,OAAKA,EAAgB,SAASD,CAAS,EAQhC,KAPE,CACL,QAAS,2CAA2CA,CAAS,wBAAwBC,EAAgB,KACnG,IAAA,CACD,EAAA,CAKP,CAKO,SAASC,EACdC,EACAH,EACwB,CACxB,GAAI,OAAOG,GAAc,SAAU,CAEjC,GAAIA,EAAU,OAAO,SAAW,EAC9B,MAAO,CACL,QAAS,4CAA4CH,CAAS,EAAA,EAKlE,GAAI,KAAK,KAAKG,CAAS,EACrB,MAAO,CACL,QAAS,eAAeA,CAAS,qDAAA,EAKjC,QAAQ,KAAKA,CAAS,GAAK,CAAC,YAAY,KAAKA,CAAS,GAExD,QAAQ,KACN,8BAA8BA,CAAS,8DAAA,CAG7C,CAEA,OAAO,IACT,CAKO,SAASC,EACdC,EACAL,EACwB,CAExB,GAAIM,EAAE,aAAaD,CAAO,EAAG,CAC3B,MAAME,EAAOF,EAAQ,KACrB,GAAIE,IAAS,aAAeA,IAAS,OACnC,MAAO,CACL,QAAS,0BAA0BA,CAAI,mBAAmBP,CAAS,8CAAA,CAGzE,CAGA,OAAIM,EAAE,mBAAmBD,CAAO,GACLG,EAAmBH,CAAO,EAE1C,CACL,QAAS,kDAAkDL,CAAS,EAAA,EAKnE,IACT,CAKA,SAASQ,EAAmBC,EAAkC,CAC5D,MAAMC,MAAW,IAEjB,UAAWC,KAAQF,EAAI,WACrB,GAAIH,EAAE,iBAAiBK,CAAI,GAAKL,EAAE,aAAaK,EAAK,GAAG,EAAG,CACxD,MAAMC,EAAMD,EAAK,IAAI,KACrB,GAAID,EAAK,IAAIE,CAAG,EACd,MAAO,GAETF,EAAK,IAAIE,CAAG,CACd,CAGF,MAAO,EACT,CAKO,SAASC,EACdb,EACAc,EACwB,CASxB,MAAMC,EARyC,CAC7C,OAAQ,CAAC,MAAM,EACf,OAAQ,CAAC,QAAS,SAAU,UAAU,EACtC,MAAO,CAAC,QAAS,UAAU,EAC3B,MAAO,CAAC,QAAS,WAAY,SAAU,SAAU,GAAG,EACpD,KAAM,CAAC,QAAS,WAAY,SAAU,SAAU,GAAG,CAAA,EAGhBf,CAAS,EAC9C,OAAIe,GAAmB,CAACA,EAAgB,SAASD,EAAY,YAAA,CAAa,EACjE,CACL,QAAS,aAAad,CAAS,2BAA2Be,EAAgB,KACxE,IAAA,CACD,4BAA4BD,CAAW,GAAA,EAIrC,IACT,CAKO,SAASE,EACdhB,EACAiB,EACwB,CACxB,GAAIjB,IAAc,WAAY,CAE5B,GAAIM,EAAE,gBAAgBW,CAAK,EACzB,MAAO,CACL,QAAS,4DAAA,EAIb,GAAIX,EAAE,yBAAyBW,CAAK,EAAG,CACrC,MAAMC,EAAOD,EAAM,WACnB,GAAIX,EAAE,kBAAkBY,CAAI,EAAG,CAC7B,GAAIA,EAAK,SAAS,OAAS,EACzB,MAAO,CACL,QACE,gEAAA,EAGN,GAAIA,EAAK,SAAS,OAAS,EACzB,MAAO,CACL,QACE,iEAAA,CAGR,KACE,OAAO,CACL,QAAS,wDAAA,CAGf,CACF,CAEA,OAAO,IACT,CAeA,MAAMC,EAAe,CAEnB,KAAM,CACJ,WACA,SACA,MACA,MACA,kBACA,SACA,SACA,OACA,MACA,MACA,MACA,QACA,OACA,SACA,QACA,SACA,YACA,MACA,UACA,SACA,MACA,SACA,UACA,MACA,QACA,YACA,WACA,KACA,aACA,OACA,OACA,SACA,QACA,SACA,UACA,SACA,OACA,OACA,YACA,WACA,UAAA,EAIF,OAAQ,CACN,oBACA,sCACA,iDACA,iDACA,oFACA,sBAAA,CAEJ,EAMO,SAASC,EAAYf,EAAiC,CAC3D,MAAMgB,EAAqB,CAAA,EAE3B,GAAIf,EAAE,mBAAmBD,CAAO,GAC9B,UAAWM,KAAQN,EAAQ,WACzB,GAAIC,EAAE,iBAAiBK,CAAI,EAAG,CAE5B,GAAIL,EAAE,aAAaK,EAAK,GAAG,EAAG,CAC5B,MAAMC,EAAMD,EAAK,IAAI,KAAK,YAAA,EAE1B,UAAWW,KAAUH,EAAa,KAChC,GAAIP,EAAI,SAASU,CAAM,EAAG,CACxBD,EAAS,KACP,uCAAuCV,EAAK,IAAI,IAAI,oCAAA,EAEtD,KACF,CAEJ,CAGA,GAAIL,EAAE,gBAAgBK,EAAK,KAAK,EAAG,CACjC,MAAMM,EAAQN,EAAK,MAAM,MAEzB,UAAWY,KAAWJ,EAAa,OACjC,GAAII,EAAQ,KAAKN,CAAK,EAAG,CACvB,MAAMO,EAAUlB,EAAE,aAAaK,EAAK,GAAG,EACnCA,EAAK,IAAI,KACT,UACJU,EAAS,KACP,uCAAuCG,CAAO,oDAAA,EAEhD,KACF,CAEJ,CACF,EAIJ,OAAOH,CACT,CChSA,MAAAI,EAAeC,EAAAA,QACb,CAACC,EAAKC,IAAY,CAChBD,EAAI,cAAc,CAAC,EAEnB,MAAME,EACJD,EAAQ,kBAAoB,oCACxBE,EAASF,EAAQ,QAAU,YAC3BG,EAAQH,EAAQ,OAAS,GACzBI,EAAWJ,EAAQ,WAAa,GAChCK,EAAUL,EAAQ,UAAY,GAEpC,MAAO,CACL,KAAM,gCAEN,QAAS,CACP,QAAS,CACP,MAAMM,EAAOC,EAAO,CAClBA,EAAM,uBAAyB,GACjC,EACA,KAAKC,EAAMD,EAAO,CAGhB,GAAIA,EAAM,oBAAsBA,EAAM,mBAAmB,KAAO,EAAG,CACjE,MAAME,EAAa,MAAM,KAAKF,EAAM,kBAAkB,EAGtD,IAAIG,EAA4B,KAChCF,EAAK,SAAS,CACZ,kBAAkBG,EAAiB,CAC7BA,EAAW,KAAK,OAAO,QAAUV,IACnCS,EAAuBC,EAE3B,CAAA,CACD,EAEGD,GAEFD,EAAW,QAASG,GAAkB,CACpC,MAAMC,EAAiBnC,EAAE,oBACvBA,EAAE,gBAAgB,OAAQA,EAAE,WAAWkC,CAAa,EAAG,EAAI,CAAA,EAE7DF,EAAqB,YAAYG,CAAc,CACjD,CAAC,CAEL,CACF,CAAA,EAGF,WAAWL,EAAMD,EAAO,CACtB,MAAMO,EAAiBN,EAAK,KAAK,eAC3BO,EAAaD,EAAe,WAG5B5B,EAAcR,EAAE,gBAAgBoC,EAAe,IAAI,EACrDA,EAAe,KAAK,KACpB,UAGEE,EAGD,CAAA,EAgBL,GAdAD,EAAW,QAASE,GAAS,CAC3B,GAAIvC,EAAE,eAAeuC,CAAI,GAAKvC,EAAE,oBAAoBuC,EAAK,IAAI,EAAG,CAC9D,MAAMC,EAAYD,EAAK,KAAK,UAAU,KAChCtC,EAAOsC,EAAK,KAAK,KAAK,KAExBC,IAAchB,GAChBc,EAAe,KAAK,CAClB,KAAAC,EACA,UAAWtC,CAAA,CACZ,CAEL,CACF,CAAC,EAEGqC,EAAe,SAAW,EAG9B,SAAW,CAAE,KAAAC,EAAM,UAAA7C,CAAA,IAAe4C,EAAgB,CAQhD,GAPIb,GACF,QAAQ,IACN,mCAAmCD,CAAM,IAAI9B,CAAS,EAAA,EAKtDgC,EAAU,CACZ,MAAMe,EAAiBhD,EAAkBC,CAAS,EAClD,GAAI+C,EACF,MAAMX,EAAK,oBAAoBW,EAAe,OAAO,EAGvD,MAAMC,EAAenC,EACnBb,EACAc,CAAA,EAEEkC,GACF,QAAQ,KAAK,kBAAkBA,EAAa,OAAO,EAAE,EAGvD,MAAMC,EAAejC,EACnBhB,EACA6C,EAAK,KAAA,EAEP,GAAII,EACF,MAAMb,EAAK,oBAAoBa,EAAa,OAAO,CAEvD,CAEAC,EACEd,EACAM,EACAG,EACA7C,EACAmC,EACAN,EACA,CACE,MAAAE,EACA,SAAAC,EACA,QAAAC,CAAA,CACF,CAEJ,CACF,CAAA,CACF,CAEJ,CACF,EAEA,SAASkB,EACPf,EACAD,EACAN,EACAW,EACA,CAEA,GAAIL,EAAM,oBAAoB,IAAIK,CAAa,EAC7C,OAGF,MAAMY,EAAUhB,EAAK,WAAYiB,GAAWA,EAAE,WAAW,EACzD,GAAI,CAACD,EAAS,OAGd,IAAIE,EAAsB,KAS1B,GARAF,EAAQ,SAAS,CACf,kBAAkBb,EAAiB,CAC7BA,EAAW,KAAK,OAAO,QAAUV,IACnCyB,EAAiBf,EAErB,CAAA,CACD,EAEGe,GASF,GAAI,CAPoBA,EAAe,KAAK,WAAW,KACpDC,GACCjD,EAAE,kBAAkBiD,CAAI,GACxBjD,EAAE,aAAaiD,EAAK,QAAQ,GAC5BA,EAAK,SAAS,OAASf,CAAA,EAGL,CAEpB,MAAMgB,EAAelD,EAAE,gBACrBA,EAAE,WAAWkC,CAAa,EAC1BlC,EAAE,WAAWkC,CAAa,CAAA,EAE5Bc,EAAe,KAAK,WAAW,KAAKE,CAAY,CAClD,MACK,CAEL,MAAMC,EAAoBnD,EAAE,kBAC1B,CACEA,EAAE,gBACAA,EAAE,WAAWkC,CAAa,EAC1BlC,EAAE,WAAWkC,CAAa,CAAA,CAC5B,EAEFlC,EAAE,cAAcuB,CAAgB,CAAA,EAElCuB,EAAQ,iBAAiB,OAAQK,CAAiB,CACpD,CAEAtB,EAAM,oBAAoB,IAAIK,CAAa,CAC7C,CAEA,SAASU,EACPd,EACAM,EACAG,EACA7C,EACAmC,EACAN,EACAD,EAKA,CACA,MAAMX,EAAQ4B,EAAK,MAGnB,GAAIvC,EAAE,gBAAgBW,CAAK,GAAKW,EAAQ,SAAU,CAChD,MAAM8B,EAAYxD,EAAkBe,EAAM,MAAOjB,CAAS,EACtD0D,GACF,QAAQ,KAAK,kBAAkBA,EAAU,OAAO,EAAE,CAEtD,CAGA,GAAIpD,EAAE,yBAAyBW,CAAK,EAAG,CACrC,MAAMC,EAAOD,EAAM,WACnB,GAAIX,EAAE,kBAAkBY,CAAI,EAAG,CAC7B,KAAM,CAAA,CAAGyC,CAAW,EAAIzC,EAAK,SAE7B,GAAIyC,GAAerD,EAAE,aAAaqD,CAAW,EAAG,CAC9C,GAAI/B,EAAQ,SAAU,CACpB,MAAMgC,EAAexD,EAAgBuD,EAAa3D,CAAS,EACvD4D,GACF,QAAQ,KAAK,kBAAkBA,EAAa,OAAO,EAAE,CAEzD,CAEIhC,EAAQ,SACUR,EAAYuC,CAAW,EAC/B,QAASE,GAAY,CAC/B,QAAQ,KAAK,kBAAkBA,CAAO,EAAE,CAC1C,CAAC,CAEL,CACF,CACF,CAGA,MAAMC,EAAwB,YAAY9D,EACvC,OAAO,CAAC,EACR,YAAA,CAAa,GAAGA,EAAU,MAAM,CAAC,CAAC,GACrCmD,EAAmBf,EAAMD,EAAON,EAAkBiC,CAAqB,EAGvE,MAAMC,EAAYrB,EAAe,WAAW,QAAQG,CAAI,EACpDkB,IAAc,IAChBrB,EAAe,WAAW,OAAOqB,EAAW,CAAC,EAI/C,MAAMC,EAAgB1D,EAAE,aACtBA,EAAE,kBACAA,EAAE,cAAc,KAAK,EACrBA,EAAE,cAAcwD,CAAqB,CAAA,EAEvC7C,CAAA,EAGFyB,EAAe,WAAW,KAAKsB,CAAa,EAExCpC,EAAQ,OACV,QAAQ,IACN,wCAAwC5B,CAAS,WAAW8D,CAAqB,EAAA,CAGvF"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { declare as
|
|
1
|
+
import { declare as h } from "@babel/helper-plugin-utils";
|
|
2
2
|
import * as e from "@babel/types";
|
|
3
|
-
function
|
|
4
|
-
const
|
|
3
|
+
function x(t) {
|
|
4
|
+
const i = [
|
|
5
5
|
"click",
|
|
6
6
|
"submit",
|
|
7
7
|
"focus",
|
|
@@ -25,79 +25,79 @@ function v(i) {
|
|
|
25
25
|
"touchend",
|
|
26
26
|
"touchmove"
|
|
27
27
|
];
|
|
28
|
-
return
|
|
29
|
-
message: `Unknown analytics directive: "analytics:${
|
|
28
|
+
return i.includes(t) ? null : {
|
|
29
|
+
message: `Unknown analytics directive: "analytics:${t}". Valid directives: ${i.join(
|
|
30
30
|
", "
|
|
31
31
|
)}`
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
function
|
|
35
|
-
if (typeof
|
|
36
|
-
if (
|
|
34
|
+
function k(t, i) {
|
|
35
|
+
if (typeof t == "string") {
|
|
36
|
+
if (t.trim().length === 0)
|
|
37
37
|
return {
|
|
38
|
-
message: `Event name cannot be empty for analytics:${
|
|
38
|
+
message: `Event name cannot be empty for analytics:${i}`
|
|
39
39
|
};
|
|
40
|
-
if (/\s/.test(
|
|
40
|
+
if (/\s/.test(t))
|
|
41
41
|
return {
|
|
42
|
-
message: `Event name "${
|
|
42
|
+
message: `Event name "${t}" contains spaces. Did you mean to use underscores?`
|
|
43
43
|
};
|
|
44
|
-
/[A-Z]/.test(
|
|
45
|
-
`[babel-plugin] Event name "${
|
|
44
|
+
/[A-Z]/.test(t) && !/^[A-Z_]+$/.test(t) && console.warn(
|
|
45
|
+
`[babel-plugin] Event name "${t}" uses camelCase. Consider using snake_case for consistency.`
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
50
|
-
function
|
|
51
|
-
if (e.isIdentifier(
|
|
52
|
-
const
|
|
53
|
-
if (
|
|
50
|
+
function E(t, i) {
|
|
51
|
+
if (e.isIdentifier(t)) {
|
|
52
|
+
const s = t.name;
|
|
53
|
+
if (s === "undefined" || s === "null")
|
|
54
54
|
return {
|
|
55
|
-
message: `Invalid payload value "${
|
|
55
|
+
message: `Invalid payload value "${s}" for analytics:${i}. Use undefined or omit the payload instead.`
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
|
-
return e.isObjectExpression(
|
|
59
|
-
message: `Duplicate keys in payload object for analytics:${
|
|
58
|
+
return e.isObjectExpression(t) && w(t) ? {
|
|
59
|
+
message: `Duplicate keys in payload object for analytics:${i}`
|
|
60
60
|
} : null;
|
|
61
61
|
}
|
|
62
|
-
function
|
|
63
|
-
const
|
|
64
|
-
for (const
|
|
65
|
-
if (e.isObjectProperty(
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
62
|
+
function w(t) {
|
|
63
|
+
const i = /* @__PURE__ */ new Set();
|
|
64
|
+
for (const s of t.properties)
|
|
65
|
+
if (e.isObjectProperty(s) && e.isIdentifier(s.key)) {
|
|
66
|
+
const n = s.key.name;
|
|
67
|
+
if (i.has(n))
|
|
68
68
|
return !0;
|
|
69
|
-
|
|
69
|
+
i.add(n);
|
|
70
70
|
}
|
|
71
71
|
return !1;
|
|
72
72
|
}
|
|
73
|
-
function
|
|
74
|
-
const
|
|
73
|
+
function $(t, i) {
|
|
74
|
+
const n = {
|
|
75
75
|
submit: ["form"],
|
|
76
76
|
change: ["input", "select", "textarea"],
|
|
77
77
|
input: ["input", "textarea"],
|
|
78
78
|
focus: ["input", "textarea", "select", "button", "a"],
|
|
79
79
|
blur: ["input", "textarea", "select", "button", "a"]
|
|
80
|
-
}[
|
|
81
|
-
return
|
|
82
|
-
message: `analytics:${
|
|
80
|
+
}[t];
|
|
81
|
+
return n && !n.includes(i.toLowerCase()) ? {
|
|
82
|
+
message: `analytics:${t} should only be used on ${n.join(
|
|
83
83
|
", "
|
|
84
|
-
)} elements, but found on <${
|
|
84
|
+
)} elements, but found on <${i}>`
|
|
85
85
|
} : null;
|
|
86
86
|
}
|
|
87
|
-
function
|
|
88
|
-
if (
|
|
89
|
-
if (e.isStringLiteral(
|
|
87
|
+
function S(t, i) {
|
|
88
|
+
if (t === "identify") {
|
|
89
|
+
if (e.isStringLiteral(i))
|
|
90
90
|
return {
|
|
91
91
|
message: "analytics:identify requires tuple syntax: [userId, traits]"
|
|
92
92
|
};
|
|
93
|
-
if (e.isJSXExpressionContainer(
|
|
94
|
-
const
|
|
95
|
-
if (e.isArrayExpression(
|
|
96
|
-
if (
|
|
93
|
+
if (e.isJSXExpressionContainer(i)) {
|
|
94
|
+
const s = i.expression;
|
|
95
|
+
if (e.isArrayExpression(s)) {
|
|
96
|
+
if (s.elements.length < 1)
|
|
97
97
|
return {
|
|
98
98
|
message: "analytics:identify requires at least userId: [userId, traits?]"
|
|
99
99
|
};
|
|
100
|
-
if (
|
|
100
|
+
if (s.elements.length > 2)
|
|
101
101
|
return {
|
|
102
102
|
message: "analytics:identify takes exactly 2 arguments: [userId, traits?]"
|
|
103
103
|
};
|
|
@@ -109,244 +109,246 @@ function C(i, t) {
|
|
|
109
109
|
}
|
|
110
110
|
return null;
|
|
111
111
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
112
|
+
const I = {
|
|
113
|
+
// Property names (case-insensitive)
|
|
114
|
+
keys: [
|
|
115
|
+
"password",
|
|
116
|
+
"passwd",
|
|
117
|
+
"pwd",
|
|
118
|
+
"ssn",
|
|
119
|
+
"social_security",
|
|
120
|
+
"social",
|
|
121
|
+
"credit",
|
|
122
|
+
"card",
|
|
123
|
+
"cvv",
|
|
124
|
+
"cvc",
|
|
125
|
+
"pin",
|
|
126
|
+
"email",
|
|
127
|
+
"mail",
|
|
128
|
+
"e_mail",
|
|
129
|
+
"phone",
|
|
130
|
+
"mobile",
|
|
131
|
+
"telephone",
|
|
132
|
+
"tel",
|
|
133
|
+
"address",
|
|
134
|
+
"street",
|
|
135
|
+
"zip",
|
|
136
|
+
"postal",
|
|
137
|
+
"zipcode",
|
|
138
|
+
"dob",
|
|
139
|
+
"birth",
|
|
140
|
+
"birthdate",
|
|
141
|
+
"birthday",
|
|
142
|
+
"ip",
|
|
143
|
+
"ip_address",
|
|
144
|
+
"ipv4",
|
|
145
|
+
"ipv6",
|
|
146
|
+
"ipaddr",
|
|
147
|
+
"token",
|
|
148
|
+
"secret",
|
|
149
|
+
"api_key",
|
|
150
|
+
"apikey",
|
|
151
|
+
"auth",
|
|
152
|
+
"name",
|
|
153
|
+
"firstname",
|
|
154
|
+
"lastname",
|
|
155
|
+
"fullname"
|
|
156
|
+
// Can be PII depending on context
|
|
157
|
+
],
|
|
158
|
+
// Value patterns (regex) - for string literals only
|
|
159
|
+
values: [
|
|
160
|
+
/\d{3}-\d{2}-\d{4}/,
|
|
161
|
+
// SSN (###-##-####)
|
|
162
|
+
/\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}/,
|
|
163
|
+
// Credit card
|
|
164
|
+
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/,
|
|
165
|
+
// Email
|
|
166
|
+
/\+?\d{1,3}[- ]?\(?\d{3}\)?[- ]?\d{3}[- ]?\d{4}/,
|
|
167
|
+
// Phone (various formats)
|
|
168
|
+
/\b\d{1,5}\s\w+\s(?:street|st|avenue|ave|road|rd|drive|dr|lane|ln|way|court|ct)\b/i,
|
|
169
|
+
// Street address
|
|
170
|
+
/\b\d{5}(?:-\d{4})?\b/
|
|
171
|
+
// ZIP code
|
|
172
|
+
]
|
|
173
|
+
};
|
|
174
|
+
function A(t) {
|
|
175
|
+
const i = [];
|
|
176
|
+
if (e.isObjectExpression(t)) {
|
|
177
|
+
for (const s of t.properties)
|
|
178
|
+
if (e.isObjectProperty(s)) {
|
|
179
|
+
if (e.isIdentifier(s.key)) {
|
|
180
|
+
const n = s.key.name.toLowerCase();
|
|
181
|
+
for (const o of I.keys)
|
|
182
|
+
if (n.includes(o)) {
|
|
183
|
+
i.push(
|
|
184
|
+
`Potential PII detected in property "${s.key.name}". Consider anonymizing this data.`
|
|
185
|
+
);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (e.isStringLiteral(s.value)) {
|
|
190
|
+
const n = s.value.value;
|
|
191
|
+
for (const o of I.values)
|
|
192
|
+
if (o.test(n)) {
|
|
193
|
+
const d = e.isIdentifier(s.key) ? s.key.name : "unknown";
|
|
194
|
+
i.push(
|
|
195
|
+
`Potential PII detected in value of "${d}". The value appears to match a sensitive pattern.`
|
|
196
|
+
);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
125
200
|
}
|
|
126
201
|
}
|
|
127
|
-
return
|
|
202
|
+
return i;
|
|
128
203
|
}
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
blur: "onBlur",
|
|
134
|
-
change: "onChange",
|
|
135
|
-
hover: "onMouseEnter",
|
|
136
|
-
scroll: "onScroll",
|
|
137
|
-
input: "onInput",
|
|
138
|
-
keydown: "onKeyDown",
|
|
139
|
-
keyup: "onKeyUp",
|
|
140
|
-
keypress: "onKeyPress",
|
|
141
|
-
mouseenter: "onMouseEnter",
|
|
142
|
-
mouseleave: "onMouseLeave",
|
|
143
|
-
mousedown: "onMouseDown",
|
|
144
|
-
mouseup: "onMouseUp",
|
|
145
|
-
touchstart: "onTouchStart",
|
|
146
|
-
touchend: "onTouchEnd",
|
|
147
|
-
touchmove: "onTouchMove"
|
|
148
|
-
}, D = ["visible", "identify", "page", "track"], z = k(
|
|
149
|
-
(i, t) => {
|
|
150
|
-
i.assertVersion(7);
|
|
151
|
-
const n = t.runtimeImport || "@solid-analytics/runtime", s = t.prefix || "analytics", a = t.debug || !1, o = t.validate !== !1, r = t.sanitize !== !1, m = t.warnPII !== !1;
|
|
204
|
+
const C = h(
|
|
205
|
+
(t, i) => {
|
|
206
|
+
t.assertVersion(7);
|
|
207
|
+
const s = i.directivesImport || "@solid-analytics/solid/directives", n = i.prefix || "analytics", o = i.debug || !1, d = i.validate !== !1, a = i.warnPII !== !1;
|
|
152
208
|
return {
|
|
153
209
|
name: "@solid-analytics/babel-plugin",
|
|
154
210
|
visitor: {
|
|
155
211
|
Program: {
|
|
156
|
-
enter(
|
|
157
|
-
|
|
212
|
+
enter(r, u) {
|
|
213
|
+
u.directivesImported = /* @__PURE__ */ new Set();
|
|
214
|
+
},
|
|
215
|
+
exit(r, u) {
|
|
216
|
+
if (u.directivesImported && u.directivesImported.size > 0) {
|
|
217
|
+
const p = Array.from(u.directivesImported);
|
|
218
|
+
let y = null;
|
|
219
|
+
r.traverse({
|
|
220
|
+
ImportDeclaration(c) {
|
|
221
|
+
c.node.source.value === s && (y = c);
|
|
222
|
+
}
|
|
223
|
+
}), y && p.forEach((c) => {
|
|
224
|
+
const f = e.expressionStatement(
|
|
225
|
+
e.unaryExpression("void", e.identifier(c), !0)
|
|
226
|
+
);
|
|
227
|
+
y.insertAfter(f);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
158
230
|
}
|
|
159
231
|
},
|
|
160
|
-
JSXElement(
|
|
161
|
-
const
|
|
162
|
-
if (
|
|
232
|
+
JSXElement(r, u) {
|
|
233
|
+
const p = r.node.openingElement, y = p.attributes, c = e.isJSXIdentifier(p.name) ? p.name.name : "Unknown", f = [];
|
|
234
|
+
if (y.forEach((l) => {
|
|
163
235
|
if (e.isJSXAttribute(l) && e.isJSXNamespacedName(l.name)) {
|
|
164
|
-
const
|
|
165
|
-
|
|
236
|
+
const m = l.name.namespace.name, b = l.name.name.name;
|
|
237
|
+
m === n && f.push({
|
|
166
238
|
attr: l,
|
|
167
239
|
directive: b
|
|
168
240
|
});
|
|
169
241
|
}
|
|
170
|
-
}),
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const b = v(f);
|
|
242
|
+
}), f.length !== 0)
|
|
243
|
+
for (const { attr: l, directive: m } of f) {
|
|
244
|
+
if (o && console.log(
|
|
245
|
+
`[babel-plugin] Found directive: ${n}:${m}`
|
|
246
|
+
), d) {
|
|
247
|
+
const b = x(m);
|
|
177
248
|
if (b)
|
|
178
|
-
throw
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
249
|
+
throw r.buildCodeFrameError(b.message);
|
|
250
|
+
const v = $(
|
|
251
|
+
m,
|
|
252
|
+
c
|
|
182
253
|
);
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
254
|
+
v && console.warn(`[babel-plugin] ${v.message}`);
|
|
255
|
+
const g = S(
|
|
256
|
+
m,
|
|
186
257
|
l.value
|
|
187
258
|
);
|
|
188
|
-
if (
|
|
189
|
-
throw
|
|
259
|
+
if (g)
|
|
260
|
+
throw r.buildCodeFrameError(g.message);
|
|
190
261
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
262
|
+
P(
|
|
263
|
+
r,
|
|
264
|
+
p,
|
|
265
|
+
l,
|
|
266
|
+
m,
|
|
267
|
+
u,
|
|
268
|
+
s,
|
|
269
|
+
{
|
|
270
|
+
debug: o,
|
|
271
|
+
validate: d,
|
|
272
|
+
warnPII: a
|
|
273
|
+
}
|
|
274
|
+
);
|
|
197
275
|
}
|
|
198
|
-
}
|
|
199
276
|
}
|
|
200
277
|
}
|
|
201
278
|
};
|
|
202
279
|
}
|
|
203
280
|
);
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
)
|
|
213
|
-
],
|
|
214
|
-
e.stringLiteral(n)
|
|
215
|
-
);
|
|
216
|
-
s.unshiftContainer("body", a), t.runtimeImportAdded = !0, t.runtimeIdentifier = e.identifier("__analytics");
|
|
217
|
-
}
|
|
218
|
-
function L(i, t, n, s, a) {
|
|
219
|
-
const o = t.value;
|
|
220
|
-
let r, m = null;
|
|
221
|
-
if (e.isStringLiteral(o)) {
|
|
222
|
-
if (r = o.value, a.validate) {
|
|
223
|
-
const u = w(r, n);
|
|
224
|
-
u && console.warn(`[babel-plugin] ${u.message}`);
|
|
281
|
+
function D(t, i, s, n) {
|
|
282
|
+
if (i.directivesImported?.has(n))
|
|
283
|
+
return;
|
|
284
|
+
const o = t.findParent((a) => a.isProgram());
|
|
285
|
+
if (!o) return;
|
|
286
|
+
let d = null;
|
|
287
|
+
if (o.traverse({
|
|
288
|
+
ImportDeclaration(a) {
|
|
289
|
+
a.node.source.value === s && (d = a);
|
|
225
290
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
291
|
+
}), d) {
|
|
292
|
+
if (!d.node.specifiers.some(
|
|
293
|
+
(r) => e.isImportSpecifier(r) && e.isIdentifier(r.imported) && r.imported.name === n
|
|
294
|
+
)) {
|
|
295
|
+
const r = e.importSpecifier(
|
|
296
|
+
e.identifier(n),
|
|
297
|
+
e.identifier(n)
|
|
298
|
+
);
|
|
299
|
+
d.node.specifiers.push(r);
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
const a = e.importDeclaration(
|
|
303
|
+
[
|
|
304
|
+
e.importSpecifier(
|
|
305
|
+
e.identifier(n),
|
|
306
|
+
e.identifier(n)
|
|
307
|
+
)
|
|
308
|
+
],
|
|
309
|
+
e.stringLiteral(s)
|
|
310
|
+
);
|
|
311
|
+
o.unshiftContainer("body", a);
|
|
312
|
+
}
|
|
313
|
+
i.directivesImported?.add(n);
|
|
314
|
+
}
|
|
315
|
+
function P(t, i, s, n, o, d, a) {
|
|
316
|
+
const r = s.value;
|
|
317
|
+
if (e.isStringLiteral(r) && a.validate) {
|
|
318
|
+
const c = k(r.value, n);
|
|
319
|
+
c && console.warn(`[babel-plugin] ${c.message}`);
|
|
320
|
+
}
|
|
321
|
+
if (e.isJSXExpressionContainer(r)) {
|
|
322
|
+
const c = r.expression;
|
|
323
|
+
if (e.isArrayExpression(c)) {
|
|
324
|
+
const [, f] = c.elements;
|
|
325
|
+
if (f && e.isExpression(f)) {
|
|
326
|
+
if (a.validate) {
|
|
327
|
+
const l = E(f, n);
|
|
328
|
+
l && console.warn(`[babel-plugin] ${l.message}`);
|
|
241
329
|
}
|
|
242
|
-
a.warnPII &&
|
|
243
|
-
console.warn(`[babel-plugin] ${
|
|
330
|
+
a.warnPII && A(f).forEach((m) => {
|
|
331
|
+
console.warn(`[babel-plugin] ${m}`);
|
|
244
332
|
});
|
|
245
333
|
}
|
|
246
|
-
}
|
|
247
|
-
r = u.value, a.sanitize && (r = x(r));
|
|
248
|
-
else if (e.isExpression(u))
|
|
249
|
-
r = u;
|
|
250
|
-
else
|
|
251
|
-
throw new Error(`Invalid value for analytics:${n}`);
|
|
252
|
-
} else
|
|
253
|
-
throw new Error(`Invalid value for analytics:${n}`);
|
|
254
|
-
const d = i.attributes.indexOf(t);
|
|
255
|
-
if (d !== -1 && i.attributes.splice(d, 1), D.includes(n)) {
|
|
256
|
-
O(
|
|
257
|
-
i,
|
|
258
|
-
n,
|
|
259
|
-
r,
|
|
260
|
-
m,
|
|
261
|
-
s,
|
|
262
|
-
a.debug
|
|
263
|
-
);
|
|
264
|
-
return;
|
|
334
|
+
}
|
|
265
335
|
}
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
), g = e.jsxAttribute(
|
|
277
|
-
e.jsxIdentifier(c),
|
|
278
|
-
e.jsxExpressionContainer(y)
|
|
336
|
+
const u = `analytics${n.charAt(0).toUpperCase()}${n.slice(1)}`;
|
|
337
|
+
D(t, o, d, u);
|
|
338
|
+
const p = i.attributes.indexOf(s);
|
|
339
|
+
p !== -1 && i.attributes.splice(p, 1);
|
|
340
|
+
const y = e.jsxAttribute(
|
|
341
|
+
e.jsxNamespacedName(
|
|
342
|
+
e.jsxIdentifier("use"),
|
|
343
|
+
e.jsxIdentifier(u)
|
|
344
|
+
),
|
|
345
|
+
r
|
|
279
346
|
);
|
|
280
|
-
i.attributes.push(
|
|
281
|
-
}
|
|
282
|
-
function h(i, t, n, s, a) {
|
|
283
|
-
const o = s.runtimeIdentifier || e.identifier("__analytics"), r = e.callExpression(
|
|
284
|
-
e.memberExpression(o, e.identifier("track")),
|
|
285
|
-
[
|
|
286
|
-
typeof t == "string" ? e.stringLiteral(t) : t,
|
|
287
|
-
n || e.identifier("undefined"),
|
|
288
|
-
e.identifier("e")
|
|
289
|
-
]
|
|
347
|
+
i.attributes.push(y), a.debug && console.log(
|
|
348
|
+
`[babel-plugin] Transformed analytics:${n} -> use:${u}`
|
|
290
349
|
);
|
|
291
|
-
return e.arrowFunctionExpression([e.identifier("e")], r);
|
|
292
|
-
}
|
|
293
|
-
function O(i, t, n, s, a, o) {
|
|
294
|
-
switch (t) {
|
|
295
|
-
case "visible":
|
|
296
|
-
const r = e.jsxAttribute(
|
|
297
|
-
e.jsxIdentifier("data-analytics-visible"),
|
|
298
|
-
e.stringLiteral(
|
|
299
|
-
typeof n == "string" ? n : JSON.stringify({ dynamic: !0 })
|
|
300
|
-
)
|
|
301
|
-
);
|
|
302
|
-
if (i.attributes.push(r), s) {
|
|
303
|
-
const g = e.jsxAttribute(
|
|
304
|
-
e.jsxIdentifier("data-analytics-visible-payload"),
|
|
305
|
-
e.jsxExpressionContainer(s)
|
|
306
|
-
);
|
|
307
|
-
i.attributes.push(g);
|
|
308
|
-
}
|
|
309
|
-
break;
|
|
310
|
-
case "identify":
|
|
311
|
-
const m = e.jsxAttribute(
|
|
312
|
-
e.jsxIdentifier("data-analytics-identify"),
|
|
313
|
-
e.jsxExpressionContainer(
|
|
314
|
-
e.arrayExpression([
|
|
315
|
-
typeof n == "string" ? e.stringLiteral(n) : n,
|
|
316
|
-
s || e.identifier("undefined")
|
|
317
|
-
])
|
|
318
|
-
)
|
|
319
|
-
);
|
|
320
|
-
i.attributes.push(m);
|
|
321
|
-
break;
|
|
322
|
-
case "page":
|
|
323
|
-
const d = e.jsxAttribute(
|
|
324
|
-
e.jsxIdentifier("data-analytics-page"),
|
|
325
|
-
e.jsxExpressionContainer(
|
|
326
|
-
e.arrayExpression([
|
|
327
|
-
typeof n == "string" ? e.stringLiteral(n) : n,
|
|
328
|
-
s || e.identifier("undefined")
|
|
329
|
-
])
|
|
330
|
-
)
|
|
331
|
-
);
|
|
332
|
-
i.attributes.push(d);
|
|
333
|
-
break;
|
|
334
|
-
case "track":
|
|
335
|
-
const c = h(
|
|
336
|
-
"track",
|
|
337
|
-
n,
|
|
338
|
-
s,
|
|
339
|
-
a
|
|
340
|
-
), y = e.jsxAttribute(
|
|
341
|
-
e.jsxIdentifier("onClick"),
|
|
342
|
-
e.jsxExpressionContainer(c)
|
|
343
|
-
);
|
|
344
|
-
i.attributes.push(y);
|
|
345
|
-
break;
|
|
346
|
-
}
|
|
347
|
-
o && console.log(`[babel-plugin] Handled special directive: ${t}`);
|
|
348
350
|
}
|
|
349
351
|
export {
|
|
350
|
-
|
|
352
|
+
C as default
|
|
351
353
|
};
|
|
352
354
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/validation.ts","../src/index.ts"],"sourcesContent":["/**\n * Validation utilities for analytics directives\n */\n\nimport * as t from \"@babel/types\";\n\nexport interface ValidationError {\n message: string;\n loc?: {\n line: number;\n column: number;\n };\n}\n\n/**\n * Validate directive name\n */\nexport function validateDirective(directive: string): ValidationError | null {\n const validDirectives = [\n \"click\",\n \"submit\",\n \"focus\",\n \"blur\",\n \"change\",\n \"hover\",\n \"scroll\",\n \"visible\",\n \"identify\",\n \"page\",\n \"track\",\n \"input\",\n \"keydown\",\n \"keyup\",\n \"keypress\",\n \"mouseenter\",\n \"mouseleave\",\n \"mousedown\",\n \"mouseup\",\n \"touchstart\",\n \"touchend\",\n \"touchmove\",\n ];\n\n if (!validDirectives.includes(directive)) {\n return {\n message: `Unknown analytics directive: \"analytics:${directive}\". Valid directives: ${validDirectives.join(\n \", \"\n )}`,\n };\n }\n\n return null;\n}\n\n/**\n * Validate event name\n */\nexport function validateEventName(\n eventName: string | t.Expression,\n directive: string\n): ValidationError | null {\n if (typeof eventName === \"string\") {\n // String event names\n if (eventName.trim().length === 0) {\n return {\n message: `Event name cannot be empty for analytics:${directive}`,\n };\n }\n\n // Warn about potential typos (spaces, special characters)\n if (/\\s/.test(eventName)) {\n return {\n message: `Event name \"${eventName}\" contains spaces. Did you mean to use underscores?`,\n };\n }\n\n // Recommend snake_case\n if (/[A-Z]/.test(eventName) && !/^[A-Z_]+$/.test(eventName)) {\n // Has mixed case but not all caps (which might be intentional)\n console.warn(\n `[babel-plugin] Event name \"${eventName}\" uses camelCase. Consider using snake_case for consistency.`\n );\n }\n }\n\n return null;\n}\n\n/**\n * Validate payload structure\n */\nexport function validatePayload(\n payload: t.Expression,\n directive: string\n): ValidationError | null {\n // Check for common mistakes\n if (t.isIdentifier(payload)) {\n const name = payload.name;\n if (name === \"undefined\" || name === \"null\") {\n return {\n message: `Invalid payload value \"${name}\" for analytics:${directive}. Use undefined or omit the payload instead.`,\n };\n }\n }\n\n // Validate object expression structure\n if (t.isObjectExpression(payload)) {\n const hasDuplicateKeys = checkDuplicateKeys(payload);\n if (hasDuplicateKeys) {\n return {\n message: `Duplicate keys in payload object for analytics:${directive}`,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Check for duplicate keys in object expression\n */\nfunction checkDuplicateKeys(obj: t.ObjectExpression): boolean {\n const keys = new Set<string>();\n\n for (const prop of obj.properties) {\n if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {\n const key = prop.key.name;\n if (keys.has(key)) {\n return true;\n }\n keys.add(key);\n }\n }\n\n return false;\n}\n\n/**\n * Validate directive is used on appropriate element\n */\nexport function validateDirectiveOnElement(\n directive: string,\n elementName: string\n): ValidationError | null {\n const restrictions: Record<string, string[]> = {\n submit: [\"form\"],\n change: [\"input\", \"select\", \"textarea\"],\n input: [\"input\", \"textarea\"],\n focus: [\"input\", \"textarea\", \"select\", \"button\", \"a\"],\n blur: [\"input\", \"textarea\", \"select\", \"button\", \"a\"],\n };\n\n const allowedElements = restrictions[directive];\n if (allowedElements && !allowedElements.includes(elementName.toLowerCase())) {\n return {\n message: `analytics:${directive} should only be used on ${allowedElements.join(\n \", \"\n )} elements, but found on <${elementName}>`,\n };\n }\n\n return null;\n}\n\n/**\n * Validate special directives (identify, page)\n */\nexport function validateSpecialDirective(\n directive: string,\n value: t.JSXAttribute[\"value\"]\n): ValidationError | null {\n if (directive === \"identify\") {\n // identify must use tuple syntax: [userId, traits]\n if (t.isStringLiteral(value)) {\n return {\n message: \"analytics:identify requires tuple syntax: [userId, traits]\",\n };\n }\n\n if (t.isJSXExpressionContainer(value)) {\n const expr = value.expression;\n if (t.isArrayExpression(expr)) {\n if (expr.elements.length < 1) {\n return {\n message:\n \"analytics:identify requires at least userId: [userId, traits?]\",\n };\n }\n if (expr.elements.length > 2) {\n return {\n message:\n \"analytics:identify takes exactly 2 arguments: [userId, traits?]\",\n };\n }\n } else {\n return {\n message: \"analytics:identify must be an array: [userId, traits?]\",\n };\n }\n }\n }\n\n return null;\n}\n\n/**\n * Sanitize event names (prevent injection)\n */\nexport function sanitizeEventName(eventName: string): string {\n // Remove any potentially dangerous characters\n return eventName\n .replace(/[^\\w\\s\\-_.]/g, \"\") // Only allow word chars, spaces, hyphens, underscores, dots\n .trim();\n}\n\n/**\n * Check for PII in payload (warning only)\n */\nexport function checkForPII(payload: t.Expression): string[] {\n const warnings: string[] = [];\n\n if (t.isObjectExpression(payload)) {\n const piiKeys = [\"password\", \"ssn\", \"credit\", \"card\", \"cvv\", \"pin\"];\n\n for (const prop of payload.properties) {\n if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {\n const key = prop.key.name.toLowerCase();\n\n if (piiKeys.some((piiKey) => key.includes(piiKey))) {\n warnings.push(\n `Potential PII detected in payload key \"${prop.key.name}\". Consider anonymizing this data.`\n );\n }\n }\n }\n }\n\n return warnings;\n}\n","/**\n * @solid-analytics/babel-plugin (Enhanced with Validation)\n *\n * Transforms analytics directives into event handlers with build-time validation\n */\n\nimport { declare } from \"@babel/helper-plugin-utils\";\nimport type { PluginObj, PluginPass } from \"@babel/core\";\nimport * as t from \"@babel/types\";\nimport {\n validateDirective,\n validateEventName,\n validatePayload,\n validateDirectiveOnElement,\n validateSpecialDirective,\n sanitizeEventName,\n checkForPII,\n} from \"./validation\";\n\n// Directive to event handler mapping (expanded in Phase 2)\nconst DIRECTIVE_TO_EVENT_MAP: Record<string, string> = {\n click: \"onClick\",\n submit: \"onSubmit\",\n focus: \"onFocus\",\n blur: \"onBlur\",\n change: \"onChange\",\n hover: \"onMouseEnter\",\n scroll: \"onScroll\",\n input: \"onInput\",\n keydown: \"onKeyDown\",\n keyup: \"onKeyUp\",\n keypress: \"onKeyPress\",\n mouseenter: \"onMouseEnter\",\n mouseleave: \"onMouseLeave\",\n mousedown: \"onMouseDown\",\n mouseup: \"onMouseUp\",\n touchstart: \"onTouchStart\",\n touchend: \"onTouchEnd\",\n touchmove: \"onTouchMove\",\n};\n\nconst SPECIAL_DIRECTIVES = [\"visible\", \"identify\", \"page\", \"track\"];\n\ninterface PluginOptions {\n runtimeImport?: string;\n prefix?: string;\n debug?: boolean;\n validate?: boolean;\n sanitize?: boolean;\n warnPII?: boolean;\n}\n\ninterface PluginState extends PluginPass {\n opts: PluginOptions;\n runtimeImportAdded?: boolean;\n runtimeIdentifier?: t.Identifier;\n}\n\nexport default declare<PluginOptions, PluginObj<PluginState>>(\n (api, options) => {\n api.assertVersion(7);\n\n const runtimeImport = options.runtimeImport || \"@solid-analytics/runtime\";\n const prefix = options.prefix || \"analytics\";\n const debug = options.debug || false;\n const validate = options.validate !== false;\n const sanitize = options.sanitize !== false;\n const warnPII = options.warnPII !== false;\n\n return {\n name: \"@solid-analytics/babel-plugin\",\n\n visitor: {\n Program: {\n enter(_path, state) {\n state.runtimeImportAdded = false;\n state.runtimeIdentifier = undefined;\n },\n },\n\n JSXElement(path, state) {\n const openingElement = path.node.openingElement;\n const attributes = openingElement.attributes;\n\n // Get element name for validation\n const elementName = t.isJSXIdentifier(openingElement.name)\n ? openingElement.name.name\n : \"Unknown\";\n\n // Find analytics directives\n const analyticsAttrs: Array<{\n attr: t.JSXAttribute;\n directive: string;\n }> = [];\n\n attributes.forEach((attr) => {\n if (t.isJSXAttribute(attr) && t.isJSXNamespacedName(attr.name)) {\n const namespace = attr.name.namespace.name;\n const name = attr.name.name.name;\n\n if (namespace === prefix) {\n analyticsAttrs.push({\n attr,\n directive: name,\n });\n }\n }\n });\n\n if (analyticsAttrs.length === 0) return;\n\n // Ensure runtime import is added\n if (!state.runtimeImportAdded) {\n addRuntimeImport(path, state, runtimeImport);\n }\n\n // Transform each analytics directive\n for (const { attr, directive } of analyticsAttrs) {\n if (debug) {\n console.log(\n `[babel-plugin] Found directive: ${prefix}:${directive}`\n );\n }\n\n // Validate directive\n if (validate) {\n const directiveError = validateDirective(directive);\n if (directiveError) {\n throw path.buildCodeFrameError(directiveError.message);\n }\n\n const elementError = validateDirectiveOnElement(\n directive,\n elementName\n );\n if (elementError) {\n console.warn(`[babel-plugin] ${elementError.message}`);\n }\n\n const specialError = validateSpecialDirective(\n directive,\n attr.value\n );\n if (specialError) {\n throw path.buildCodeFrameError(specialError.message);\n }\n }\n\n transformDirective(openingElement, attr, directive, state, {\n debug,\n validate,\n sanitize,\n warnPII,\n });\n }\n },\n },\n };\n }\n);\n\nfunction addRuntimeImport(\n path: any,\n state: PluginState,\n runtimeImport: string\n) {\n const program = path.findParent((p: any) => p.isProgram());\n if (!program) return;\n\n const importDeclaration = t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"__analytics\"),\n t.identifier(\"__analytics\")\n ),\n ],\n t.stringLiteral(runtimeImport)\n );\n\n program.unshiftContainer(\"body\", importDeclaration);\n state.runtimeImportAdded = true;\n state.runtimeIdentifier = t.identifier(\"__analytics\");\n}\n\nfunction transformDirective(\n openingElement: t.JSXOpeningElement,\n attr: t.JSXAttribute,\n directive: string,\n state: PluginState,\n options: {\n debug: boolean;\n validate: boolean;\n sanitize: boolean;\n warnPII: boolean;\n }\n) {\n const value = attr.value;\n let eventName: string | t.Expression;\n let payload: t.Expression | null = null;\n\n if (t.isStringLiteral(value)) {\n eventName = value.value;\n\n // Validate event name\n if (options.validate) {\n const nameError = validateEventName(eventName, directive);\n if (nameError) {\n console.warn(`[babel-plugin] ${nameError.message}`);\n }\n }\n\n // Sanitize event name\n if (options.sanitize && typeof eventName === \"string\") {\n eventName = sanitizeEventName(eventName);\n }\n } else if (t.isJSXExpressionContainer(value)) {\n const expr = value.expression;\n\n if (t.isArrayExpression(expr)) {\n const [eventExpr, payloadExpr] = expr.elements;\n\n if (eventExpr && t.isStringLiteral(eventExpr)) {\n eventName = eventExpr.value;\n\n if (options.sanitize) {\n eventName = sanitizeEventName(eventName);\n }\n } else if (eventExpr && t.isExpression(eventExpr)) {\n eventName = eventExpr;\n } else {\n throw new Error(`Invalid event name in analytics:${directive}`);\n }\n\n if (payloadExpr && t.isExpression(payloadExpr)) {\n payload = payloadExpr;\n\n // Validate payload\n if (options.validate) {\n const payloadError = validatePayload(payload, directive);\n if (payloadError) {\n console.warn(`[babel-plugin] ${payloadError.message}`);\n }\n }\n\n // Check for PII\n if (options.warnPII) {\n const piiWarnings = checkForPII(payload);\n piiWarnings.forEach((warning) => {\n console.warn(`[babel-plugin] ${warning}`);\n });\n }\n }\n } else if (t.isStringLiteral(expr)) {\n eventName = expr.value;\n if (options.sanitize) {\n eventName = sanitizeEventName(eventName);\n }\n } else if (t.isExpression(expr)) {\n eventName = expr;\n } else {\n throw new Error(`Invalid value for analytics:${directive}`);\n }\n } else {\n throw new Error(`Invalid value for analytics:${directive}`);\n }\n\n // Remove the analytics directive attribute by finding it\n const attrIndex = openingElement.attributes.indexOf(attr);\n if (attrIndex !== -1) {\n openingElement.attributes.splice(attrIndex, 1);\n }\n\n // Handle special directives\n if (SPECIAL_DIRECTIVES.includes(directive)) {\n handleSpecialDirective(\n openingElement,\n directive,\n eventName,\n payload,\n state,\n options.debug\n );\n return;\n }\n\n // Get the corresponding DOM event\n const domEvent = DIRECTIVE_TO_EVENT_MAP[directive];\n if (!domEvent && options.validate) {\n throw new Error(`Unknown analytics directive: ${directive}`);\n }\n\n // If validation is disabled and we don't know this directive, skip it\n if (!domEvent) {\n return;\n }\n\n // Create the event handler\n const handler = createEventHandler(\n directive,\n eventName,\n payload,\n state,\n options.debug\n );\n\n // Add the event handler as a JSX attribute\n const eventAttr = t.jsxAttribute(\n t.jsxIdentifier(domEvent),\n t.jsxExpressionContainer(handler)\n );\n\n openingElement.attributes.push(eventAttr);\n\n if (options.debug) {\n console.log(`[babel-plugin] Transformed ${directive} -> ${domEvent}`);\n }\n}\n\nfunction createEventHandler(\n _directive: string,\n eventName: string | t.Expression,\n payload: t.Expression | null,\n state: PluginState,\n _debug: boolean\n): t.ArrowFunctionExpression {\n const runtimeId = state.runtimeIdentifier || t.identifier(\"__analytics\");\n\n const trackCall = t.callExpression(\n t.memberExpression(runtimeId, t.identifier(\"track\")),\n [\n typeof eventName === \"string\" ? t.stringLiteral(eventName) : eventName,\n payload || t.identifier(\"undefined\"),\n t.identifier(\"e\"),\n ]\n );\n\n return t.arrowFunctionExpression([t.identifier(\"e\")], trackCall);\n}\n\nfunction handleSpecialDirective(\n openingElement: t.JSXOpeningElement,\n directive: string,\n eventName: string | t.Expression,\n payload: t.Expression | null,\n state: PluginState,\n debug: boolean\n) {\n switch (directive) {\n case \"visible\":\n const visibleAttr = t.jsxAttribute(\n t.jsxIdentifier(\"data-analytics-visible\"),\n t.stringLiteral(\n typeof eventName === \"string\"\n ? eventName\n : JSON.stringify({ dynamic: true })\n )\n );\n openingElement.attributes.push(visibleAttr);\n\n if (payload) {\n const payloadAttr = t.jsxAttribute(\n t.jsxIdentifier(\"data-analytics-visible-payload\"),\n t.jsxExpressionContainer(payload)\n );\n openingElement.attributes.push(payloadAttr);\n }\n break;\n\n case \"identify\":\n const identifyAttr = t.jsxAttribute(\n t.jsxIdentifier(\"data-analytics-identify\"),\n t.jsxExpressionContainer(\n t.arrayExpression([\n typeof eventName === \"string\"\n ? t.stringLiteral(eventName)\n : eventName,\n payload || t.identifier(\"undefined\"),\n ])\n )\n );\n openingElement.attributes.push(identifyAttr);\n break;\n\n case \"page\":\n const pageAttr = t.jsxAttribute(\n t.jsxIdentifier(\"data-analytics-page\"),\n t.jsxExpressionContainer(\n t.arrayExpression([\n typeof eventName === \"string\"\n ? t.stringLiteral(eventName)\n : eventName,\n payload || t.identifier(\"undefined\"),\n ])\n )\n );\n openingElement.attributes.push(pageAttr);\n break;\n\n case \"track\":\n const trackHandler = createEventHandler(\n \"track\",\n eventName,\n payload,\n state,\n debug\n );\n\n const trackAttr = t.jsxAttribute(\n t.jsxIdentifier(\"onClick\"),\n t.jsxExpressionContainer(trackHandler)\n );\n openingElement.attributes.push(trackAttr);\n break;\n }\n\n if (debug) {\n console.log(`[babel-plugin] Handled special directive: ${directive}`);\n }\n}\n"],"names":["validateDirective","directive","validDirectives","validateEventName","eventName","validatePayload","payload","t","name","checkDuplicateKeys","obj","keys","prop","key","validateDirectiveOnElement","elementName","allowedElements","validateSpecialDirective","value","expr","sanitizeEventName","checkForPII","warnings","piiKeys","piiKey","DIRECTIVE_TO_EVENT_MAP","SPECIAL_DIRECTIVES","index","declare","api","options","runtimeImport","prefix","debug","validate","sanitize","warnPII","_path","state","path","openingElement","attributes","analyticsAttrs","attr","namespace","addRuntimeImport","directiveError","elementError","specialError","transformDirective","program","p","importDeclaration","nameError","eventExpr","payloadExpr","payloadError","warning","attrIndex","handleSpecialDirective","domEvent","handler","createEventHandler","eventAttr","_directive","_debug","runtimeId","trackCall","visibleAttr","payloadAttr","identifyAttr","pageAttr","trackHandler","trackAttr"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/validation.ts","../src/index.ts"],"sourcesContent":["/**\n * Validation utilities for analytics directives\n */\n\nimport * as t from \"@babel/types\";\n\nexport interface ValidationError {\n message: string;\n loc?: {\n line: number;\n column: number;\n };\n}\n\n/**\n * Validate directive name\n */\nexport function validateDirective(directive: string): ValidationError | null {\n const validDirectives = [\n \"click\",\n \"submit\",\n \"focus\",\n \"blur\",\n \"change\",\n \"hover\",\n \"scroll\",\n \"visible\",\n \"identify\",\n \"page\",\n \"track\",\n \"input\",\n \"keydown\",\n \"keyup\",\n \"keypress\",\n \"mouseenter\",\n \"mouseleave\",\n \"mousedown\",\n \"mouseup\",\n \"touchstart\",\n \"touchend\",\n \"touchmove\",\n ];\n\n if (!validDirectives.includes(directive)) {\n return {\n message: `Unknown analytics directive: \"analytics:${directive}\". Valid directives: ${validDirectives.join(\n \", \"\n )}`,\n };\n }\n\n return null;\n}\n\n/**\n * Validate event name\n */\nexport function validateEventName(\n eventName: string | t.Expression,\n directive: string\n): ValidationError | null {\n if (typeof eventName === \"string\") {\n // String event names\n if (eventName.trim().length === 0) {\n return {\n message: `Event name cannot be empty for analytics:${directive}`,\n };\n }\n\n // Warn about potential typos (spaces, special characters)\n if (/\\s/.test(eventName)) {\n return {\n message: `Event name \"${eventName}\" contains spaces. Did you mean to use underscores?`,\n };\n }\n\n // Recommend snake_case\n if (/[A-Z]/.test(eventName) && !/^[A-Z_]+$/.test(eventName)) {\n // Has mixed case but not all caps (which might be intentional)\n console.warn(\n `[babel-plugin] Event name \"${eventName}\" uses camelCase. Consider using snake_case for consistency.`\n );\n }\n }\n\n return null;\n}\n\n/**\n * Validate payload structure\n */\nexport function validatePayload(\n payload: t.Expression,\n directive: string\n): ValidationError | null {\n // Check for common mistakes\n if (t.isIdentifier(payload)) {\n const name = payload.name;\n if (name === \"undefined\" || name === \"null\") {\n return {\n message: `Invalid payload value \"${name}\" for analytics:${directive}. Use undefined or omit the payload instead.`,\n };\n }\n }\n\n // Validate object expression structure\n if (t.isObjectExpression(payload)) {\n const hasDuplicateKeys = checkDuplicateKeys(payload);\n if (hasDuplicateKeys) {\n return {\n message: `Duplicate keys in payload object for analytics:${directive}`,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Check for duplicate keys in object expression\n */\nfunction checkDuplicateKeys(obj: t.ObjectExpression): boolean {\n const keys = new Set<string>();\n\n for (const prop of obj.properties) {\n if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {\n const key = prop.key.name;\n if (keys.has(key)) {\n return true;\n }\n keys.add(key);\n }\n }\n\n return false;\n}\n\n/**\n * Validate directive is used on appropriate element\n */\nexport function validateDirectiveOnElement(\n directive: string,\n elementName: string\n): ValidationError | null {\n const restrictions: Record<string, string[]> = {\n submit: [\"form\"],\n change: [\"input\", \"select\", \"textarea\"],\n input: [\"input\", \"textarea\"],\n focus: [\"input\", \"textarea\", \"select\", \"button\", \"a\"],\n blur: [\"input\", \"textarea\", \"select\", \"button\", \"a\"],\n };\n\n const allowedElements = restrictions[directive];\n if (allowedElements && !allowedElements.includes(elementName.toLowerCase())) {\n return {\n message: `analytics:${directive} should only be used on ${allowedElements.join(\n \", \"\n )} elements, but found on <${elementName}>`,\n };\n }\n\n return null;\n}\n\n/**\n * Validate special directives (identify, page)\n */\nexport function validateSpecialDirective(\n directive: string,\n value: t.JSXAttribute[\"value\"]\n): ValidationError | null {\n if (directive === \"identify\") {\n // identify must use tuple syntax: [userId, traits]\n if (t.isStringLiteral(value)) {\n return {\n message: \"analytics:identify requires tuple syntax: [userId, traits]\",\n };\n }\n\n if (t.isJSXExpressionContainer(value)) {\n const expr = value.expression;\n if (t.isArrayExpression(expr)) {\n if (expr.elements.length < 1) {\n return {\n message:\n \"analytics:identify requires at least userId: [userId, traits?]\",\n };\n }\n if (expr.elements.length > 2) {\n return {\n message:\n \"analytics:identify takes exactly 2 arguments: [userId, traits?]\",\n };\n }\n } else {\n return {\n message: \"analytics:identify must be an array: [userId, traits?]\",\n };\n }\n }\n }\n\n return null;\n}\n\n/**\n * Sanitize event names (prevent injection)\n */\nexport function sanitizeEventName(eventName: string): string {\n // Remove any potentially dangerous characters\n return eventName\n .replace(/[^\\w\\s\\-_.]/g, \"\") // Only allow word chars, spaces, hyphens, underscores, dots\n .trim();\n}\n\n/**\n * PII detection patterns\n */\nconst PII_PATTERNS = {\n // Property names (case-insensitive)\n keys: [\n \"password\",\n \"passwd\",\n \"pwd\",\n \"ssn\",\n \"social_security\",\n \"social\",\n \"credit\",\n \"card\",\n \"cvv\",\n \"cvc\",\n \"pin\",\n \"email\",\n \"mail\",\n \"e_mail\",\n \"phone\",\n \"mobile\",\n \"telephone\",\n \"tel\",\n \"address\",\n \"street\",\n \"zip\",\n \"postal\",\n \"zipcode\",\n \"dob\",\n \"birth\",\n \"birthdate\",\n \"birthday\",\n \"ip\",\n \"ip_address\",\n \"ipv4\",\n \"ipv6\",\n \"ipaddr\",\n \"token\",\n \"secret\",\n \"api_key\",\n \"apikey\",\n \"auth\",\n \"name\",\n \"firstname\",\n \"lastname\",\n \"fullname\", // Can be PII depending on context\n ],\n\n // Value patterns (regex) - for string literals only\n values: [\n /\\d{3}-\\d{2}-\\d{4}/, // SSN (###-##-####)\n /\\d{4}[- ]?\\d{4}[- ]?\\d{4}[- ]?\\d{4}/, // Credit card\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/, // Email\n /\\+?\\d{1,3}[- ]?\\(?\\d{3}\\)?[- ]?\\d{3}[- ]?\\d{4}/, // Phone (various formats)\n /\\b\\d{1,5}\\s\\w+\\s(?:street|st|avenue|ave|road|rd|drive|dr|lane|ln|way|court|ct)\\b/i, // Street address\n /\\b\\d{5}(?:-\\d{4})?\\b/, // ZIP code\n ],\n} as const;\n\n/**\n * Check for PII in payload (warning only)\n * Enhanced with comprehensive pattern matching\n */\nexport function checkForPII(payload: t.Expression): string[] {\n const warnings: string[] = [];\n\n if (t.isObjectExpression(payload)) {\n for (const prop of payload.properties) {\n if (t.isObjectProperty(prop)) {\n // Check property key names\n if (t.isIdentifier(prop.key)) {\n const key = prop.key.name.toLowerCase();\n\n for (const piiKey of PII_PATTERNS.keys) {\n if (key.includes(piiKey)) {\n warnings.push(\n `Potential PII detected in property \"${prop.key.name}\". Consider anonymizing this data.`\n );\n break; // Only warn once per property\n }\n }\n }\n\n // Check string literal values for PII patterns\n if (t.isStringLiteral(prop.value)) {\n const value = prop.value.value;\n\n for (const pattern of PII_PATTERNS.values) {\n if (pattern.test(value)) {\n const keyName = t.isIdentifier(prop.key)\n ? prop.key.name\n : \"unknown\";\n warnings.push(\n `Potential PII detected in value of \"${keyName}\". The value appears to match a sensitive pattern.`\n );\n break; // Only warn once per value\n }\n }\n }\n }\n }\n }\n\n return warnings;\n}\n","/**\n * @solid-analytics/babel-plugin (Enhanced with Validation)\n *\n * Transforms analytics directives into event handlers with build-time validation\n */\n\nimport { declare } from \"@babel/helper-plugin-utils\";\nimport type { PluginObj, PluginPass } from \"@babel/core\";\nimport * as t from \"@babel/types\";\nimport {\n validateDirective,\n validateEventName,\n validatePayload,\n validateDirectiveOnElement,\n validateSpecialDirective,\n checkForPII,\n} from \"./validation\";\n\ninterface PluginOptions {\n directivesImport?: string;\n prefix?: string;\n debug?: boolean;\n validate?: boolean;\n sanitize?: boolean;\n warnPII?: boolean;\n}\n\ninterface PluginState extends PluginPass {\n opts: PluginOptions;\n directivesImported?: Set<string>;\n}\n\nexport default declare<PluginOptions, PluginObj<PluginState>>(\n (api, options) => {\n api.assertVersion(7);\n\n const directivesImport =\n options.directivesImport || \"@solid-analytics/solid/directives\";\n const prefix = options.prefix || \"analytics\";\n const debug = options.debug || false;\n const validate = options.validate !== false;\n const warnPII = options.warnPII !== false;\n\n return {\n name: \"@solid-analytics/babel-plugin\",\n\n visitor: {\n Program: {\n enter(_path, state) {\n state.directivesImported = new Set();\n },\n exit(path, state) {\n // After processing all JSX, add void references for all imported directives\n // to prevent tree-shaking\n if (state.directivesImported && state.directivesImported.size > 0) {\n const directives = Array.from(state.directivesImported);\n\n // Find the directives import\n let directivesImportPath: any = null;\n path.traverse({\n ImportDeclaration(importPath: any) {\n if (importPath.node.source.value === directivesImport) {\n directivesImportPath = importPath;\n }\n },\n });\n\n if (directivesImportPath) {\n // Add void statements for all directives after the import\n directives.forEach((directiveName) => {\n const voidExpression = t.expressionStatement(\n t.unaryExpression(\"void\", t.identifier(directiveName), true)\n );\n directivesImportPath.insertAfter(voidExpression);\n });\n }\n }\n },\n },\n\n JSXElement(path, state) {\n const openingElement = path.node.openingElement;\n const attributes = openingElement.attributes;\n\n // Get element name for validation\n const elementName = t.isJSXIdentifier(openingElement.name)\n ? openingElement.name.name\n : \"Unknown\";\n\n // Find analytics directives\n const analyticsAttrs: Array<{\n attr: t.JSXAttribute;\n directive: string;\n }> = [];\n\n attributes.forEach((attr) => {\n if (t.isJSXAttribute(attr) && t.isJSXNamespacedName(attr.name)) {\n const namespace = attr.name.namespace.name;\n const name = attr.name.name.name;\n\n if (namespace === prefix) {\n analyticsAttrs.push({\n attr,\n directive: name,\n });\n }\n }\n });\n\n if (analyticsAttrs.length === 0) return;\n\n // Transform each analytics directive\n for (const { attr, directive } of analyticsAttrs) {\n if (debug) {\n console.log(\n `[babel-plugin] Found directive: ${prefix}:${directive}`\n );\n }\n\n // Validate directive\n if (validate) {\n const directiveError = validateDirective(directive);\n if (directiveError) {\n throw path.buildCodeFrameError(directiveError.message);\n }\n\n const elementError = validateDirectiveOnElement(\n directive,\n elementName\n );\n if (elementError) {\n console.warn(`[babel-plugin] ${elementError.message}`);\n }\n\n const specialError = validateSpecialDirective(\n directive,\n attr.value\n );\n if (specialError) {\n throw path.buildCodeFrameError(specialError.message);\n }\n }\n\n transformDirective(\n path,\n openingElement,\n attr,\n directive,\n state,\n directivesImport,\n {\n debug,\n validate,\n warnPII,\n }\n );\n }\n },\n },\n };\n }\n);\n\nfunction addDirectiveImport(\n path: any,\n state: PluginState,\n directivesImport: string,\n directiveName: string\n) {\n // Check if we've already imported this directive\n if (state.directivesImported?.has(directiveName)) {\n return;\n }\n\n const program = path.findParent((p: any) => p.isProgram());\n if (!program) return;\n\n // Check if there's already an import from directivesImport\n let existingImport: any = null;\n program.traverse({\n ImportDeclaration(importPath: any) {\n if (importPath.node.source.value === directivesImport) {\n existingImport = importPath;\n }\n },\n });\n\n if (existingImport) {\n // Check if this specific directive is already imported\n const alreadyImported = existingImport.node.specifiers.some(\n (spec: any) =>\n t.isImportSpecifier(spec) &&\n t.isIdentifier(spec.imported) &&\n spec.imported.name === directiveName\n );\n\n if (!alreadyImported) {\n // Add to existing import\n const newSpecifier = t.importSpecifier(\n t.identifier(directiveName),\n t.identifier(directiveName)\n );\n existingImport.node.specifiers.push(newSpecifier);\n }\n } else {\n // Create new import\n const importDeclaration = t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(directiveName),\n t.identifier(directiveName)\n ),\n ],\n t.stringLiteral(directivesImport)\n );\n program.unshiftContainer(\"body\", importDeclaration);\n }\n\n state.directivesImported?.add(directiveName);\n}\n\nfunction transformDirective(\n path: any,\n openingElement: t.JSXOpeningElement,\n attr: t.JSXAttribute,\n directive: string,\n state: PluginState,\n directivesImport: string,\n options: {\n debug: boolean;\n validate: boolean;\n warnPII: boolean;\n }\n) {\n const value = attr.value;\n\n // Validate and sanitize if string value\n if (t.isStringLiteral(value) && options.validate) {\n const nameError = validateEventName(value.value, directive);\n if (nameError) {\n console.warn(`[babel-plugin] ${nameError.message}`);\n }\n }\n\n // Validate payload if array syntax\n if (t.isJSXExpressionContainer(value)) {\n const expr = value.expression;\n if (t.isArrayExpression(expr)) {\n const [, payloadExpr] = expr.elements;\n\n if (payloadExpr && t.isExpression(payloadExpr)) {\n if (options.validate) {\n const payloadError = validatePayload(payloadExpr, directive);\n if (payloadError) {\n console.warn(`[babel-plugin] ${payloadError.message}`);\n }\n }\n\n if (options.warnPII) {\n const piiWarnings = checkForPII(payloadExpr);\n piiWarnings.forEach((warning) => {\n console.warn(`[babel-plugin] ${warning}`);\n });\n }\n }\n }\n }\n\n // Ensure directive function is imported\n const directiveFunctionName = `analytics${directive\n .charAt(0)\n .toUpperCase()}${directive.slice(1)}`;\n addDirectiveImport(path, state, directivesImport, directiveFunctionName);\n\n // Remove the analytics:* attribute\n const attrIndex = openingElement.attributes.indexOf(attr);\n if (attrIndex !== -1) {\n openingElement.attributes.splice(attrIndex, 1);\n }\n\n // Add the use:* directive\n const directiveAttr = t.jsxAttribute(\n t.jsxNamespacedName(\n t.jsxIdentifier(\"use\"),\n t.jsxIdentifier(directiveFunctionName)\n ),\n value\n );\n\n openingElement.attributes.push(directiveAttr);\n\n if (options.debug) {\n console.log(\n `[babel-plugin] Transformed analytics:${directive} -> use:${directiveFunctionName}`\n );\n }\n}\n"],"names":["validateDirective","directive","validDirectives","validateEventName","eventName","validatePayload","payload","t","name","checkDuplicateKeys","obj","keys","prop","key","validateDirectiveOnElement","elementName","allowedElements","validateSpecialDirective","value","expr","PII_PATTERNS","checkForPII","warnings","piiKey","pattern","keyName","index","declare","api","options","directivesImport","prefix","debug","validate","warnPII","_path","state","path","directives","directivesImportPath","importPath","directiveName","voidExpression","openingElement","attributes","analyticsAttrs","attr","namespace","directiveError","elementError","specialError","transformDirective","addDirectiveImport","program","p","existingImport","spec","newSpecifier","importDeclaration","nameError","payloadExpr","payloadError","warning","directiveFunctionName","attrIndex","directiveAttr"],"mappings":"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"}
|
package/package.json
CHANGED