-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
no-dynamic-createelement
rule (#8656)
**Related Issue:** #8651 ## Summary This adds a custom ESLint rule to ensure Stencil bundles and auto-defines child components created via `document.createElement`.
- Loading branch information
Showing
8 changed files
with
132 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
packages/eslint-plugin-calcite-components/docs/no-dynamic-createelement.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# no-dynamic-createelement | ||
|
||
Helps prevent usage of dynamic element tags used with `createElement`. This ensures the component is properly bundled and auto-defined in Stencil's `components` output target. | ||
|
||
## Config | ||
|
||
No config is needed | ||
|
||
## Usage | ||
|
||
```json | ||
{ "@esri/calcite-components/no-dynamic-createelement": "error" } | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,15 @@ | ||
import banEvents from "./ban-events"; | ||
import banPropsOnHost from "./ban-props-on-host"; | ||
import enforceRefLastProp from "./enforce-ref-last-prop"; | ||
import noDynamicCreateelement from "./no-dynamic-createelement"; | ||
import requireEventEmitterType from "./require-event-emitter-type"; | ||
import strictBooleanAttributes from "./strict-boolean-attributes"; | ||
|
||
export default { | ||
"ban-events": banEvents, | ||
"ban-props-on-host": banPropsOnHost, | ||
"enforce-ref-last-prop": enforceRefLastProp, | ||
"no-dynamic-createelement": noDynamicCreateelement, | ||
"require-event-emitter-type": requireEventEmitterType, | ||
"strict-boolean-attributes": strictBooleanAttributes, | ||
}; |
46 changes: 46 additions & 0 deletions
46
packages/eslint-plugin-calcite-components/src/rules/no-dynamic-createelement.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { Rule } from "eslint"; | ||
|
||
function isCreateElement(node) { | ||
return ( | ||
node?.callee?.type === "MemberExpression" && | ||
node?.callee?.object?.name === "document" && | ||
node?.callee?.property?.name === "createElement" && | ||
node.arguments.length >= 1 | ||
); | ||
} | ||
|
||
function isStaticValue(arg) { | ||
return arg.type === "Literal" || (arg.type === "TemplateLiteral" && arg.expressions.length === 0); | ||
} | ||
|
||
const rule: Rule.RuleModule = { | ||
meta: { | ||
docs: { | ||
description: | ||
"This ensures supporting components created with `document.createElement()` are auto-defined in Stencil's `components` output target.", | ||
recommended: true, | ||
}, | ||
fixable: "code", | ||
schema: [], | ||
type: "problem", | ||
}, | ||
|
||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
if (!node.arguments[0] || isStaticValue(node.arguments[0])) { | ||
return; | ||
} | ||
|
||
if (isCreateElement(node)) { | ||
return context.report({ | ||
node, | ||
message: "Calls to document.createElement() should use string literals", | ||
}); | ||
} | ||
}, | ||
}; | ||
}, | ||
}; | ||
|
||
export default rule; |
19 changes: 19 additions & 0 deletions
19
...ite-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.good.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// @ts-nocheck | ||
@Component({ tag: "sample-tag" }) | ||
export class SampleTag { | ||
@Prop() | ||
type: "one" | "two" = "one"; | ||
|
||
connectedCallback() { | ||
const child = | ||
this.type === "one" | ||
? document.createElement("my-component-1") | ||
: document.createElement("my-component-2"); | ||
this.el.append(child); | ||
this.internalEl = child; | ||
} | ||
|
||
disconnectedCallback() { | ||
this.internalEl.remove(); | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
...cite-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import rule from "../../../../src/rules/no-dynamic-createelement"; | ||
import { ruleTester } from "stencil-eslint-core"; | ||
import * as path from "path"; | ||
import * as fs from "fs"; | ||
|
||
const projectPath = path.resolve(__dirname, "../../../tsconfig.json"); | ||
|
||
describe("no-dynamic-createelement rule", () => { | ||
const files = { | ||
good: path.resolve(__dirname, "no-dynamic-createelement.good.tsx"), | ||
wrong: path.resolve(__dirname, "no-dynamic-createelement.wrong.tsx"), | ||
}; | ||
ruleTester(projectPath).run("no-dynamic-createelement", rule, { | ||
valid: [ | ||
{ | ||
code: fs.readFileSync(files.good, "utf8"), | ||
filename: files.good, | ||
}, | ||
], | ||
|
||
invalid: [ | ||
{ | ||
code: fs.readFileSync(files.wrong, "utf8"), | ||
filename: files.wrong, | ||
errors: 1, | ||
}, | ||
], | ||
}); | ||
}); |
16 changes: 16 additions & 0 deletions
16
...te-components/tests/lib/rules/no-dynamic-createelement/no-dynamic-createelement.wrong.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// @ts-nocheck | ||
@Component({ tag: "sample-tag" }) | ||
export class SampleTag { | ||
@Prop() | ||
type: "one" | "two" = "one"; | ||
|
||
connectedCallback() { | ||
const child = document.createElement(this.type === "one" ? "my-component-1" : "my-component-2"); | ||
this.el.append(child); | ||
this.internalEl = child; | ||
} | ||
|
||
disconnectedCallback() { | ||
this.internalEl.remove(); | ||
} | ||
} |