eslint-plugin-use-agnostic 1.0.0 → 1.1.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/README.md CHANGED
@@ -78,7 +78,7 @@ Base url and aliased import paths are currently resolved under the assumption th
78
78
 
79
79
  It is up to you to confirm that your Agnostic Modules are indeed agnostic, meaning that they have neither server- nor client-side code. `eslint-plugin-use-agnostic`, at least at this time, does not do this verification for you.
80
80
 
81
- It is also up to you to ensure, as outlined above, that **you avoid** exporting React components along with other logics within the same module, which may derail the linting in some cases. Separating exporting React components within their own modules ending with a JSX file extension, from exporting other logics within modules that don't end with a JSX file extension, is crucial for distinguishing between Components Modules and Logics Modules respectively.
81
+ It is also up to you to ensure, as outlined above, that **you avoid** exporting React components along with other logics within the same modules (unless you have to per the design of your current framework), which may derail the linting in some cases. Separating exporting React components within their own modules ending with a JSX file extension, from exporting other logics within modules that don't end with a JSX file extension, is crucial for distinguishing between Components Modules and Logics Modules respectively.
82
82
 
83
83
  The import rules are designed to be as permissive as possible, allowing for more obscure use cases as long as they are non-breaking. However, it is still your responsibility as a developer to, within a file, not mix in incompatible ways code that cannot compose.
84
84
 
@@ -81,15 +81,14 @@ export const resolveImportPath = (currentDir, importPath, cwd) => {
81
81
  return null; // not found
82
82
  };
83
83
 
84
- /* getASTFromResolvedPath */ // for agnostic20
85
- // Note: For agnostic20, I need the AST so that the directive can be picked up on any line as long as it is the first statement of the file.
84
+ /* getSourceCodeFromFilePath */
86
85
 
87
86
  /**
88
- * Gets the ESLint-generated Abstract Syntax Tree of a file from its resolved path.
87
+ * Gets the ESLint-generated SourceCode object of a file from its resolved path.
89
88
  * @param {string} resolvedPath The resolved path of the file.
90
- * @returns The ESLint-generated AST (Abstract Syntax Tree) of the file.
89
+ * @returns The ESLint-generated SourceCode object of the file.
91
90
  */
92
- export const getASTFromFilePath = (resolvedPath) => {
91
+ export const getSourceCodeFromFilePath = (resolvedPath) => {
93
92
  // ensures each instance of the function is based on its own linter
94
93
  // (just in case somehow some linters were running concurrently)
95
94
  const linter = new Linter();
@@ -97,32 +96,10 @@ export const getASTFromFilePath = (resolvedPath) => {
97
96
  const text = fs.readFileSync(resolvedPath, "utf8");
98
97
  // utilizes linter.verify ...
99
98
  linter.verify(text, { languageOptions: typeScriptAndJSXCompatible });
100
- // ... to retrieve the raw code as a SourceCode object ...
99
+ // ... to retrieve the raw code as a SourceCode object
101
100
  const code = linter.getSourceCode();
102
- // ... from which to extract the ESLint-generated AST
103
- const ast = code.ast;
104
101
 
105
- return ast;
106
- };
107
-
108
- /* getImportedFileFirstLine */ // for directive21
109
- // Note: For directive21, I prioritize reading from the file system for performance, foregoing the retrieval of the source code comments for imported modules, since the Directive-First Architecture imposes that the first line of the file is reserved for its commented directive.
110
-
111
- /**
112
- * Gets the first line of code of the imported module.
113
- * @param {string} resolvedImportPath The resolved path of the imported module.
114
- * @returns The first line of the imported module.
115
- */
116
- export const getImportedFileFirstLine = (resolvedImportPath) => {
117
- // gets the code of the import
118
- const importedFileContent = fs.readFileSync(resolvedImportPath, "utf8");
119
- // gets the first line of the code of the import
120
- const importedFileFirstLine = importedFileContent
121
- .trim()
122
- .split("\n")[0]
123
- .trim(); // the line itself needs to be trimmed too
124
-
125
- return importedFileFirstLine;
102
+ return code;
126
103
  };
127
104
 
128
105
  /* highlightFirstLineOfCode */
@@ -12,7 +12,7 @@ import {
12
12
  isImportBlocked as commonsIsImportBlocked,
13
13
  makeMessageFromCurrentFileResolvedDirective,
14
14
  findSpecificViolationMessage as commonsFindSpecificViolationMessage,
15
- getASTFromFilePath,
15
+ getSourceCodeFromFilePath,
16
16
  } from "../../../_commons/utilities/helpers.js";
17
17
 
18
18
  /**
@@ -85,12 +85,12 @@ export const getDirectiveFromCurrentModule = (context) => {
85
85
  * - `'use server'` denotes a Server Functions Module.
86
86
  * - `'use agnostic'` denotes an Agnostic Module (formerly Shared Module).
87
87
  * - `null` denotes a server-by-default module, ideally a Server Module.
88
- * @param {string} resolvedImportPath The resolved path of the import.
88
+ * @param {string} resolvedPath The resolved path of the imported module.
89
89
  * @returns The directive, or lack thereof via `null`. The lack of a directive is considered server-by-default.
90
90
  */
91
- export const getDirectiveFromImportedModule = (resolvedImportPath) => {
91
+ export const getDirectiveFromImportedModule = (resolvedPath) => {
92
92
  // the AST of the imported module
93
- const ast = getASTFromFilePath(resolvedImportPath);
93
+ const ast = getSourceCodeFromFilePath(resolvedPath).ast;
94
94
 
95
95
  return getDirectiveFromModule(ast);
96
96
  };
@@ -119,48 +119,6 @@ export const commentedDirectiveMessage = "commentedDirectiveMessage";
119
119
  export const specificViolationMessage = "specificViolationMessage";
120
120
  export const specificFailure = "specificFailure";
121
121
 
122
- /* commentedDirectives_4RawImplementations */
123
-
124
- // all formatting styles as an array of [prefix, quote, suffix]
125
- /** @type {CommentStyles} */
126
- const commentStyles = Object.freeze([
127
- Object.freeze([`// `, `'`, ``]), // V1: `// 'directive'`
128
- Object.freeze([`// `, `"`, ``]), // V2: `// "directive"`
129
- Object.freeze([`\/\* `, `'`, ` \*\/`]), // V3: `/* 'directive' */`
130
- Object.freeze([`\/\* `, `"`, ` \*\/`]), // V4: `/* "directive" */`
131
- ]);
132
-
133
- /**
134
- * Makes the array of all four accepted commented directive implementations on a directive basis.
135
- * @template {CommentedDirective} T
136
- * @param {T} directive The commented directive.
137
- * @returns The array of formatted commented directives.
138
- */
139
- const make4RawImplementations = (directive) => {
140
- /** @type {readonly [`// '${T}'`, `// "${T}""`, `\/\* '${T}' \*\/`, `\/\* "${T}"" \*\/`]} */
141
- const rawImplementations = Object.freeze(
142
- commentStyles.map(
143
- ([prefix, quote, suffix]) =>
144
- `${prefix}${quote}${directive}${quote}${suffix}`
145
- )
146
- );
147
-
148
- return rawImplementations;
149
- };
150
-
151
- export const commentedDirectives_4RawImplementations = Object.freeze({
152
- [USE_SERVER_LOGICS]: make4RawImplementations(USE_SERVER_LOGICS),
153
- [USE_CLIENT_LOGICS]: make4RawImplementations(USE_CLIENT_LOGICS),
154
- [USE_AGNOSTIC_LOGICS]: make4RawImplementations(USE_AGNOSTIC_LOGICS),
155
- [USE_SERVER_COMPONENTS]: make4RawImplementations(USE_SERVER_COMPONENTS),
156
- [USE_CLIENT_COMPONENTS]: make4RawImplementations(USE_CLIENT_COMPONENTS),
157
- [USE_AGNOSTIC_COMPONENTS]: make4RawImplementations(USE_AGNOSTIC_COMPONENTS),
158
- [USE_SERVER_FUNCTIONS]: make4RawImplementations(USE_SERVER_FUNCTIONS),
159
- [USE_CLIENT_CONTEXTS]: make4RawImplementations(USE_CLIENT_CONTEXTS),
160
- [USE_AGNOSTIC_CONDITIONS]: make4RawImplementations(USE_AGNOSTIC_CONDITIONS),
161
- [USE_AGNOSTIC_STRATEGIES]: make4RawImplementations(USE_AGNOSTIC_STRATEGIES),
162
- });
163
-
164
122
  /* commentedDirectives_verificationReports */
165
123
 
166
124
  const MODULES_MARKED_WITH_THE_ = "modules marked with the";
@@ -39,7 +39,7 @@ Here, "{{ ${currentFileCommentedDirective} }}" and "{{ ${importedFileCommentedDi
39
39
  [importBreaksCommentedImportRulesMessageId]: `{{ ${commentedDirectiveMessage} }}
40
40
  In this case, {{ ${specificViolationMessage} }} `,
41
41
  [noCommentedDirective]: `No commented directive detected.
42
- All targeted modules need to be marked with their respective directives (\`// "use server logics"\`, etc.) for the purpose of this linting rule. `,
42
+ All targeted modules need to be marked with their respective directives (\`// "use server logics"\`, etc.) for the purpose of this linting rule, evaluated from the first JavaScript comment starting on the first column within the first three lines of a module. `,
43
43
  [commentedDirectiveVerificationFailed]: `The commented directive could not pass verification due to an incompatible combination with its file extension.
44
44
  In this context, {{ ${specificFailure} }} `,
45
45
  [importNotStrategized]: `Imports from Agnostic Strategies Modules must be strategized (\`/* @serverLogics */\`, etc.).
@@ -1,11 +1,9 @@
1
1
  import { exportNotStrategized } from "../../../_commons/constants/bases.js";
2
2
  import {
3
- USE_AGNOSTIC_LOGICS,
4
3
  USE_AGNOSTIC_STRATEGIES,
5
4
  commentedDirectivesArray,
6
5
  strategiesArray,
7
6
  commentedDirectives_extensionRules,
8
- commentedDirectives_4RawImplementations,
9
7
  commentedStrategies_commentedDirectives,
10
8
  commentedDirectives_blockedImports,
11
9
  } from "../constants/bases.js";
@@ -14,10 +12,11 @@ import {
14
12
  isImportBlocked as commonsIsImportBlocked,
15
13
  makeMessageFromCurrentFileResolvedDirective,
16
14
  findSpecificViolationMessage as commonsFindSpecificViolationMessage,
17
- getImportedFileFirstLine,
15
+ getSourceCodeFromFilePath,
18
16
  } from "../../../_commons/utilities/helpers.js";
19
17
 
20
18
  /**
19
+ * @typedef {import('../../../../types/directive21/_commons/typedefs.js').SourceCode} SourceCode
21
20
  * @typedef {import('../../../../types/directive21/_commons/typedefs.js').Context} Context
22
21
  * @typedef {import('../../../../types/directive21/_commons/typedefs.js').CommentedDirective} CommentedDirective
23
22
  * @typedef {import('../../../../types/directive21/_commons/typedefs.js').CommentedDirectiveWithoutUseAgnosticStrategies} CommentedDirectiveWithoutUseAgnosticStrategies
@@ -28,7 +27,7 @@ import {
28
27
  * @typedef {import('../../../../types/directive21/_commons/typedefs.js').ExportDefaultDeclaration} ExportDefaultDeclaration
29
28
  */
30
29
 
31
- /* getCommentedDirectiveFromCurrentModule */
30
+ /* getCommentedDirectiveFromSourceCode */
32
31
 
33
32
  /**
34
33
  * Detects whether a string is single- or double-quoted.
@@ -70,7 +69,7 @@ const stripDoubleQuotes = (string) => {
70
69
  };
71
70
 
72
71
  /**
73
- * Gets the commented directive of the current module.
72
+ * Gets the commented directive of a module from its ESLint SourceCode object.
74
73
  *
75
74
  * Accepted directives for the default Directive-First Architecture are (single or double quotes included):
76
75
  * - `'use server logics'`, `"use server logics"` denoting a Server Logics Module.
@@ -84,19 +83,26 @@ const stripDoubleQuotes = (string) => {
84
83
  * - `'use client contexts'`, `"use client contexts"` denoting a Client Contexts Module.
85
84
  * - `'use agnostic conditions'`, `"use agnostic conditions"` denoting an Agnostic Conditions Module.
86
85
  * - `'use agnostic strategies'`, `"use agnostic strategies"` denoting an Agnostic Strategies Module.
87
- * @param {Context} context The ESLint rule's `context` object.
86
+ * @param {SourceCode} sourceCode The ESLint SourceCode object.
88
87
  * @returns The commented directive, or lack thereof via `null`. Given the strictness of this architecture, the lack of a directive is considered a mistake. (Though rules may provide the opportunity to declare a default, and configs with preset defaults may become provided.)
89
88
  */
90
- export const getCommentedDirectiveFromCurrentModule = (context) => {
89
+ export const getCommentedDirectiveFromSourceCode = (sourceCode) => {
91
90
  // gets the first comment from the source code
92
- const firstComment = context.sourceCode.getAllComments()[0];
91
+ const rawFirstComment = sourceCode.getAllComments()[0];
92
+
93
+ const firstComment =
94
+ rawFirstComment.type === "Shebang"
95
+ ? sourceCode.getAllComments()[1]
96
+ : rawFirstComment;
93
97
 
94
98
  // returns null early if there is no first comment
95
99
  if (!firstComment) return null;
96
100
 
97
- // returns null early if the first comment is not on the first line and the first column
98
- if (firstComment.loc.start.line !== 1 || firstComment.loc.start.column !== 0)
99
- return null;
101
+ // returns null early if the first comment is not on one of the first three lines
102
+ if (firstComment.loc.start.line > 3) return null;
103
+
104
+ // returns null early if the first comment is not on the first column
105
+ if (firstComment.loc.start.column !== 0) return null;
100
106
 
101
107
  // gets the trimmed raw value of the first comment
102
108
  const rawValue = firstComment.value.trim();
@@ -119,6 +125,60 @@ export const getCommentedDirectiveFromCurrentModule = (context) => {
119
125
  return commentedDirective;
120
126
  };
121
127
 
128
+ /* getCommentedDirectiveFromCurrentModule */
129
+
130
+ /**
131
+ * Gets the commented directive of the current module.
132
+ *
133
+ * Accepted directives for the default Directive-First Architecture are (single or double quotes included):
134
+ * - `'use server logics'`, `"use server logics"` denoting a Server Logics Module.
135
+ * - `'use client logics'`, `"use client logics"` denoting a Client Logics Module.
136
+ * - `'use agnostic logics'`, `"use agnostic logics"` denoting an Agnostic Logics Module.
137
+ * - `'use server components'`, `"use server components"` denoting a Server Components Module.
138
+ * - `'use client components'`, `"use client components"` denoting a Client Components Module.
139
+ * - `'use agnostic components'`, `"use agnostic components"` denoting an Agnostic Components Module.
140
+ * - `'use agnostic logics'`, `"use agnostic logics"` denoting an Agnostic Logics Module.
141
+ * - `'use server functions'`, `"use server functions"` denoting a Server Functions Module.
142
+ * - `'use client contexts'`, `"use client contexts"` denoting a Client Contexts Module.
143
+ * - `'use agnostic conditions'`, `"use agnostic conditions"` denoting an Agnostic Conditions Module.
144
+ * - `'use agnostic strategies'`, `"use agnostic strategies"` denoting an Agnostic Strategies Module.
145
+ * @param {Context} context The ESLint rule's `context` object.
146
+ * @returns The commented directive, or lack thereof via `null`. Given the strictness of this architecture, the lack of a directive is considered a mistake. (Though rules may provide the opportunity to declare a default, and configs with preset defaults may become provided.)
147
+ */
148
+ export const getCommentedDirectiveFromCurrentModule = (context) => {
149
+ const sourceCode = context.sourceCode;
150
+ const commentedDirective = getCommentedDirectiveFromSourceCode(sourceCode);
151
+
152
+ return commentedDirective;
153
+ };
154
+
155
+ /* getCommentedDirectiveFromImportedModule */
156
+
157
+ /**
158
+ * Gets the commented directive of the imported module.
159
+ *
160
+ * Accepted directives for the default Directive-First Architecture are (single or double quotes included):
161
+ * - `'use server logics'`, `"use server logics"` denoting a Server Logics Module.
162
+ * - `'use client logics'`, `"use client logics"` denoting a Client Logics Module.
163
+ * - `'use agnostic logics'`, `"use agnostic logics"` denoting an Agnostic Logics Module.
164
+ * - `'use server components'`, `"use server components"` denoting a Server Components Module.
165
+ * - `'use client components'`, `"use client components"` denoting a Client Components Module.
166
+ * - `'use agnostic components'`, `"use agnostic components"` denoting an Agnostic Components Module.
167
+ * - `'use agnostic logics'`, `"use agnostic logics"` denoting an Agnostic Logics Module.
168
+ * - `'use server functions'`, `"use server functions"` denoting a Server Functions Module.
169
+ * - `'use client contexts'`, `"use client contexts"` denoting a Client Contexts Module.
170
+ * - `'use agnostic conditions'`, `"use agnostic conditions"` denoting an Agnostic Conditions Module.
171
+ * - `'use agnostic strategies'`, `"use agnostic strategies"` denoting an Agnostic Strategies Module.
172
+ * @param {string} resolvedPath The resolved path of the imported module.
173
+ * @returns The commented directive, or lack thereof via `null`. Given the strictness of this architecture, the lack of a directive is considered a mistake. (Though rules may provide the opportunity to declare a default, and configs with preset defaults may become provided.)
174
+ */
175
+ export const getCommentedDirectiveFromImportedModule = (resolvedPath) => {
176
+ const sourceCode = getSourceCodeFromFilePath(resolvedPath);
177
+ const commentedDirective = getCommentedDirectiveFromSourceCode(sourceCode);
178
+
179
+ return commentedDirective;
180
+ };
181
+
122
182
  /* getVerifiedCommentedDirective */
123
183
 
124
184
  /**
@@ -148,46 +208,6 @@ export const getVerifiedCommentedDirective = (directive, extension) => {
148
208
  return null; // verification failed
149
209
  };
150
210
 
151
- /* getCommentedDirectiveFromImportedModule */
152
-
153
- /**
154
- * Gets the commented directive of the imported module.
155
- *
156
- * Accepted directives for the default Directive-First Architecture are (single or double quotes included):
157
- * - `'use server logics'`, `"use server logics"` denoting a Server Logics Module.
158
- * - `'use client logics'`, `"use client logics"` denoting a Client Logics Module.
159
- * - `'use agnostic logics'`, `"use agnostic logics"` denoting an Agnostic Logics Module.
160
- * - `'use server components'`, `"use server components"` denoting a Server Components Module.
161
- * - `'use client components'`, `"use client components"` denoting a Client Components Module.
162
- * - `'use agnostic components'`, `"use agnostic components"` denoting an Agnostic Components Module.
163
- * - `'use agnostic logics'`, `"use agnostic logics"` denoting an Agnostic Logics Module.
164
- * - `'use server functions'`, `"use server functions"` denoting a Server Functions Module.
165
- * - `'use client contexts'`, `"use client contexts"` denoting a Client Contexts Module.
166
- * - `'use agnostic conditions'`, `"use agnostic conditions"` denoting an Agnostic Conditions Module.
167
- * - `'use agnostic strategies'`, `"use agnostic strategies"` denoting an Agnostic Strategies Module.
168
- * @param {string} resolvedImportPath The resolved path of the import.
169
- * @returns The commented directive, or lack thereof via `null`. Given the strictness of this architecture, the lack of a directive is considered a mistake. (Though rules may provide the opportunity to declare a default, and configs with preset defaults may become provided.)
170
- */
171
- export const getCommentedDirectiveFromImportedModule = (resolvedImportPath) => {
172
- // gets the first line of the code of the import
173
- const importedFileFirstLine = getImportedFileFirstLine(resolvedImportPath);
174
-
175
- // sees if the first line includes any of the directives and finds the directive that it includes, with USE_AGNOSTIC_LOGICS as a default
176
- const includedDirective = commentedDirectivesArray.reduce((acc, curr) => {
177
- if (importedFileFirstLine.includes(curr)) return curr;
178
- else return acc;
179
- }, USE_AGNOSTIC_LOGICS);
180
-
181
- // sees if the first line is strictly equal to one of the four raw implementations of the commented directive and returns that directive if true or null if false
182
- if (
183
- commentedDirectives_4RawImplementations[includedDirective].some(
184
- (raw) => raw === importedFileFirstLine
185
- )
186
- )
187
- return includedDirective;
188
- else return null;
189
- };
190
-
191
211
  /* getStrategizedDirective */
192
212
 
193
213
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-use-agnostic",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Highlights problematic server-client imports in projects made with the Fullstack React Architecture.",
5
5
  "keywords": [
6
6
  "eslint",