@omnifyjp/ts 6.3.11 → 6.3.12
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/php/index.js +8 -2
- package/dist/php/php-style.d.ts +24 -0
- package/dist/php/php-style.js +152 -0
- package/package.json +1 -1
package/dist/php/index.js
CHANGED
|
@@ -35,7 +35,7 @@ import { generateControllers } from './controller-generator.js';
|
|
|
35
35
|
import { generateServices } from './service-generator.js';
|
|
36
36
|
import { generateRoutes } from './route-generator.js';
|
|
37
37
|
import { generateEnums } from './enum-generator.js';
|
|
38
|
-
import { normalizeNotOperator, normalizePhpBlankLines, normalizePhpStatements, normalizeTopLevelUses } from './php-style.js';
|
|
38
|
+
import { normalizeFullyQualifiedClassRefs, normalizeNotOperator, normalizePhpBlankLines, normalizePhpStatements, normalizeTopLevelUses } from './php-style.js';
|
|
39
39
|
import { generateOpenApi } from './openapi-generator.js';
|
|
40
40
|
import { generateArchitectureDoc } from './architecture-doc-generator.js';
|
|
41
41
|
export { derivePhpConfig } from './types.js';
|
|
@@ -116,6 +116,12 @@ export function generatePhp(data, overrides) {
|
|
|
116
116
|
// unused imports dropped, the rest ordered. Doing it once, centrally, is why
|
|
117
117
|
// no template has to remember: the two rules fired on almost every layer.
|
|
118
118
|
return files.map((file) => file.path.endsWith('.php')
|
|
119
|
-
? {
|
|
119
|
+
? {
|
|
120
|
+
...file,
|
|
121
|
+
content: normalizePhpBlankLines(normalizePhpStatements(normalizeNotOperator(
|
|
122
|
+
// Hoist before ordering: the hoist adds imports the sort then
|
|
123
|
+
// puts in place, and the prune drops any it made redundant.
|
|
124
|
+
normalizeTopLevelUses(normalizeFullyQualifiedClassRefs(file.content))))),
|
|
125
|
+
}
|
|
120
126
|
: file);
|
|
121
127
|
}
|
package/dist/php/php-style.d.ts
CHANGED
|
@@ -81,3 +81,27 @@ export declare function normalizePhpStatements(content: string): string;
|
|
|
81
81
|
* comparisons, not negations.
|
|
82
82
|
*/
|
|
83
83
|
export declare function normalizeNotOperator(content: string): string;
|
|
84
|
+
/**
|
|
85
|
+
* `fully_qualified_strict_types` with `import_symbols` — Pint's `laravel`
|
|
86
|
+
* preset turns an inline `\App\Models\Post::class` into a `use` plus the short
|
|
87
|
+
* name, in code and in docblock types alike.
|
|
88
|
+
*
|
|
89
|
+
* The generators write the FQN because it always resolves; Pint then rewrites
|
|
90
|
+
* it, and in a project of 146 schemas that was 193 files changing on every
|
|
91
|
+
* `generate` and changing back on every `pint` (reported after #174 fixed the
|
|
92
|
+
* Resource layer the same way — the remaining layers write FQNs from their own
|
|
93
|
+
* templates rather than through `createClassImporter`).
|
|
94
|
+
*
|
|
95
|
+
* Applied centrally, after the templates and before the import block is
|
|
96
|
+
* sorted, because the rule fires on every layer that names a class.
|
|
97
|
+
*
|
|
98
|
+
* What it leaves alone, because Pint does too:
|
|
99
|
+
* - a class in this file's own namespace keeps just its short name, no
|
|
100
|
+
* import — PHP resolves it already;
|
|
101
|
+
* - a short name already bound to a different FQN keeps the FQN, since an
|
|
102
|
+
* import would change which class the name means;
|
|
103
|
+
* - a root-namespace `\Exception`, which has no namespace to import from;
|
|
104
|
+
* - anything inside a string literal or a `//` comment: a morph-map value
|
|
105
|
+
* `'App\Models\Post'` is data, not a reference.
|
|
106
|
+
*/
|
|
107
|
+
export declare function normalizeFullyQualifiedClassRefs(content: string): string;
|
package/dist/php/php-style.js
CHANGED
|
@@ -249,3 +249,155 @@ export function normalizeNotOperator(content) {
|
|
|
249
249
|
}
|
|
250
250
|
return out;
|
|
251
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* `fully_qualified_strict_types` with `import_symbols` — Pint's `laravel`
|
|
254
|
+
* preset turns an inline `\App\Models\Post::class` into a `use` plus the short
|
|
255
|
+
* name, in code and in docblock types alike.
|
|
256
|
+
*
|
|
257
|
+
* The generators write the FQN because it always resolves; Pint then rewrites
|
|
258
|
+
* it, and in a project of 146 schemas that was 193 files changing on every
|
|
259
|
+
* `generate` and changing back on every `pint` (reported after #174 fixed the
|
|
260
|
+
* Resource layer the same way — the remaining layers write FQNs from their own
|
|
261
|
+
* templates rather than through `createClassImporter`).
|
|
262
|
+
*
|
|
263
|
+
* Applied centrally, after the templates and before the import block is
|
|
264
|
+
* sorted, because the rule fires on every layer that names a class.
|
|
265
|
+
*
|
|
266
|
+
* What it leaves alone, because Pint does too:
|
|
267
|
+
* - a class in this file's own namespace keeps just its short name, no
|
|
268
|
+
* import — PHP resolves it already;
|
|
269
|
+
* - a short name already bound to a different FQN keeps the FQN, since an
|
|
270
|
+
* import would change which class the name means;
|
|
271
|
+
* - a root-namespace `\Exception`, which has no namespace to import from;
|
|
272
|
+
* - anything inside a string literal or a `//` comment: a morph-map value
|
|
273
|
+
* `'App\Models\Post'` is data, not a reference.
|
|
274
|
+
*/
|
|
275
|
+
export function normalizeFullyQualifiedClassRefs(content) {
|
|
276
|
+
const nsMatch = content.match(/^namespace\s+([^;]+);/m);
|
|
277
|
+
const currentNamespace = nsMatch ? nsMatch[1].trim() : '';
|
|
278
|
+
// Seed with what the file already imports so a hoist cannot shadow one.
|
|
279
|
+
const bound = new Map();
|
|
280
|
+
// …and with the class this file declares. A Resource base named
|
|
281
|
+
// `DepartmentResource` that references the project's own
|
|
282
|
+
// `App\Http\Resources\DepartmentResource` must keep the FQN: importing a
|
|
283
|
+
// name the file already declares is a PHP fatal, not a style choice (#179).
|
|
284
|
+
const declared = content.match(/^(?:final\s+|abstract\s+)?(?:class|interface|trait|enum)\s+([A-Za-z0-9_]+)/m);
|
|
285
|
+
if (declared) {
|
|
286
|
+
bound.set(declared[1], currentNamespace ? `${currentNamespace}\\${declared[1]}` : declared[1]);
|
|
287
|
+
}
|
|
288
|
+
for (const line of content.split('\n')) {
|
|
289
|
+
if (/^(class|trait|interface|enum|abstract|final|return)\b/.test(line))
|
|
290
|
+
break;
|
|
291
|
+
const m = line.match(/^use\s+(?:function\s+|const\s+)?([^;]+);$/);
|
|
292
|
+
if (!m)
|
|
293
|
+
continue;
|
|
294
|
+
const spec = m[1].trim();
|
|
295
|
+
if (spec.includes(' as ')) {
|
|
296
|
+
bound.set(spec.slice(spec.lastIndexOf(' as ') + 4).trim(), spec.slice(0, spec.indexOf(' as ')).trim());
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
bound.set(spec.slice(spec.lastIndexOf('\\') + 1), spec);
|
|
300
|
+
}
|
|
301
|
+
const toImport = new Set();
|
|
302
|
+
// The leading `\` must start the reference: in `namespace App\Models\Base;`
|
|
303
|
+
// the inner `\Models\Base` is part of one name, not a reference to hoist.
|
|
304
|
+
const rewrite = (segment) => segment.replace(/(?<![\w\\])\\([A-Z][A-Za-z0-9_]*(?:\\[A-Z][A-Za-z0-9_]*)+)/g, (whole, fqn) => {
|
|
305
|
+
const shortName = fqn.slice(fqn.lastIndexOf('\\') + 1);
|
|
306
|
+
const ns = fqn.slice(0, fqn.lastIndexOf('\\'));
|
|
307
|
+
if (ns === currentNamespace)
|
|
308
|
+
return shortName;
|
|
309
|
+
const existing = bound.get(shortName);
|
|
310
|
+
if (existing && existing !== fqn)
|
|
311
|
+
return whole;
|
|
312
|
+
if (!existing)
|
|
313
|
+
bound.set(shortName, fqn);
|
|
314
|
+
toImport.add(fqn);
|
|
315
|
+
return shortName;
|
|
316
|
+
});
|
|
317
|
+
// Walk the file so strings and line comments keep their FQNs verbatim.
|
|
318
|
+
// Block comments are rewritten: that is where the docblock types live.
|
|
319
|
+
// Code accumulates in a buffer that is rewritten when flushed, so a `//`
|
|
320
|
+
// in the middle of a line is still recognised as a comment.
|
|
321
|
+
let out = '';
|
|
322
|
+
let code = '';
|
|
323
|
+
const flush = () => { out += rewrite(code); code = ''; };
|
|
324
|
+
let i = 0;
|
|
325
|
+
while (i < content.length) {
|
|
326
|
+
const ch = content[i];
|
|
327
|
+
const next = content[i + 1];
|
|
328
|
+
// A declaration names itself — rewriting it would rename the file's own
|
|
329
|
+
// namespace or eat an import.
|
|
330
|
+
if ((i === 0 || content[i - 1] === '\n')
|
|
331
|
+
&& (content.startsWith('use ', i) || content.startsWith('namespace ', i))) {
|
|
332
|
+
flush();
|
|
333
|
+
const lineEnd = content.indexOf('\n', i);
|
|
334
|
+
const stop = lineEnd === -1 ? content.length : lineEnd + 1;
|
|
335
|
+
out += content.slice(i, stop);
|
|
336
|
+
i = stop;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (ch === '/' && next === '/') {
|
|
340
|
+
flush();
|
|
341
|
+
const lineEnd = content.indexOf('\n', i);
|
|
342
|
+
const stop = lineEnd === -1 ? content.length : lineEnd;
|
|
343
|
+
out += content.slice(i, stop);
|
|
344
|
+
i = stop;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (ch === '/' && next === '*') {
|
|
348
|
+
flush();
|
|
349
|
+
const end = content.indexOf('*/', i + 2);
|
|
350
|
+
const stop = end === -1 ? content.length : end + 2;
|
|
351
|
+
out += rewrite(content.slice(i, stop));
|
|
352
|
+
i = stop;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (ch === "'" || ch === '"') {
|
|
356
|
+
flush();
|
|
357
|
+
const quote = ch;
|
|
358
|
+
let j = i + 1;
|
|
359
|
+
while (j < content.length) {
|
|
360
|
+
if (content[j] === '\\') {
|
|
361
|
+
j += 2;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (content[j] === quote) {
|
|
365
|
+
j++;
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
j++;
|
|
369
|
+
}
|
|
370
|
+
out += content.slice(i, j);
|
|
371
|
+
i = j;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
code += ch;
|
|
375
|
+
i++;
|
|
376
|
+
}
|
|
377
|
+
flush();
|
|
378
|
+
if (toImport.size === 0)
|
|
379
|
+
return out;
|
|
380
|
+
const lines = out.split('\n');
|
|
381
|
+
let insertAt = -1;
|
|
382
|
+
for (let k = 0; k < lines.length; k++) {
|
|
383
|
+
if (/^use\s+[A-Za-z_\\][^;]*;\s*$/.test(lines[k])) {
|
|
384
|
+
insertAt = k;
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
if (/^namespace\s+/.test(lines[k]))
|
|
388
|
+
insertAt = k + 1;
|
|
389
|
+
if (/^(class|trait|interface|enum|abstract|final|return)\b/.test(lines[k]))
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
if (insertAt < 0)
|
|
393
|
+
return out;
|
|
394
|
+
const useLines = [...toImport].map((fqn) => `use ${fqn};`);
|
|
395
|
+
if (/^use\s+/.test(lines[insertAt] ?? '')) {
|
|
396
|
+
// Land inside the existing block; normalizeTopLevelUses orders it after.
|
|
397
|
+
lines.splice(insertAt, 0, ...useLines);
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
lines.splice(insertAt, 0, '', ...useLines);
|
|
401
|
+
}
|
|
402
|
+
return lines.join('\n');
|
|
403
|
+
}
|