diff --git a/package-lock.json b/package-lock.json index 67157724..a5586a23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4388,7 +4388,7 @@ }, "recipes/fs-access-mode-constants": { "name": "@nodejs/fs-access-mode-constants", - "version": "1.0.1", + "version": "1.0.3", "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" diff --git a/recipes/fs-access-mode-constants/codemod.yaml b/recipes/fs-access-mode-constants/codemod.yaml index ad85b5d4..93d8b377 100644 --- a/recipes/fs-access-mode-constants/codemod.yaml +++ b/recipes/fs-access-mode-constants/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/fs-access-mode-constants" -version: "1.0.2" +version: "1.0.3" description: Handle DEP0176 via transforming imports of `fs.F_OK`, `fs.R_OK`, `fs.W_OK`, `fs.X_OK` from the root `fs` module to `fs.constants`. author: nekojanai (Jana) license: MIT diff --git a/recipes/fs-access-mode-constants/package.json b/recipes/fs-access-mode-constants/package.json index fd81fe9e..3d7687e1 100644 --- a/recipes/fs-access-mode-constants/package.json +++ b/recipes/fs-access-mode-constants/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/fs-access-mode-constants", - "version": "1.0.1", + "version": "1.0.3", "description": "Handle DEP0176 via transforming imports of `fs.F_OK`, `fs.R_OK`, `fs.W_OK`, `fs.X_OK` from the root `fs` module to `fs.constants`.", "type": "module", "scripts": { diff --git a/recipes/fs-access-mode-constants/src/workflow.ts b/recipes/fs-access-mode-constants/src/workflow.ts index 5f72ff63..1c44ec9a 100644 --- a/recipes/fs-access-mode-constants/src/workflow.ts +++ b/recipes/fs-access-mode-constants/src/workflow.ts @@ -1,79 +1,277 @@ -import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; -import { getNodeImportStatements } from '@nodejs/codemod-utils/ast-grep/import-statement'; -import type { Edit, SgRoot } from '@codemod.com/jssg-types/main'; +import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; +import { updateBinding } from '@nodejs/codemod-utils/ast-grep/update-binding'; +import type { Edit, SgNode, SgRoot } from '@codemod.com/jssg-types/main'; import type Js from '@codemod.com/jssg-types/langs/javascript'; -const patterns = ['F_OK', 'R_OK', 'W_OK', 'X_OK']; +const PATTERN_SET = new Set(['F_OK', 'R_OK', 'W_OK', 'X_OK']); -export default function tranform(root: SgRoot): string | null { +type BindingMapping = { + local: string; + replacement: string; +}; + +type RemovedBinding = { + imported: string; + local: string; +}; + +export default function transform(root: SgRoot): string | null { const rootNode = root.root(); const edits: Edit[] = []; + const localBindings = new Map(); + const namespaceBindings = new Map(); - const requireStatements = getNodeRequireCalls(root, 'fs'); + const importStatements = getModuleDependencies(root, 'fs'); - for (const statement of requireStatements) { - const objectPattern = statement.find({ - rule: { kind: 'object_pattern' }, - }); + if (!importStatements) return null; + + for (const statement of importStatements) { + const promisesBinding = resolveBindingPath(statement, '$.promises'); + const rewritten = rewriteBindings(statement, promisesBinding); + edits.push(...rewritten.edits); + + for (const mapping of rewritten.mappings) { + localBindings.set(mapping.local, mapping.replacement); + } + + for (const pattern of PATTERN_SET) { + const resolved = resolveBindingPath(statement, `$.${pattern}`); + if (!resolved?.includes('.') || resolved.includes('.constants.')) { + continue; + } - if (objectPattern) { - let objPatArr = objectPattern - .findAll({ - rule: { kind: 'shorthand_property_identifier_pattern' }, - }) - .map((v) => v.text()); - objPatArr = objPatArr.filter((v) => !patterns.includes(v)); - objPatArr.push('constants'); - edits.push(objectPattern.replace(`{ ${objPatArr.join(', ')} }`)); + namespaceBindings.set( + resolved, + resolved.replace(`.${pattern}`, `.constants.${pattern}`), + ); } } - const importStatements = getNodeImportStatements(root, 'fs'); - let promisesImportName = ''; + applyNamespaceReplacements(rootNode, edits, namespaceBindings); + applyLocalReplacements(rootNode, edits, localBindings); - for (const statement of importStatements) { - const objectPattern = statement.find({ - rule: { kind: 'named_imports' }, + if (edits.length === 0) return null; + + return rootNode.commitEdits(edits); +} + +function rewriteBindings( + statement: SgNode, + promisesBinding: string, +): { edits: Edit[]; mappings: BindingMapping[] } { + const objectPattern = statement.find({ + rule: { kind: 'object_pattern' }, + }); + + if (objectPattern) + return rewriteObjectPattern(statement, objectPattern, promisesBinding); + + const namedImports = statement.find({ + rule: { kind: 'named_imports' }, + }); + + if (namedImports) + return rewriteNamedImports(statement, namedImports, promisesBinding); + + return { edits: [], mappings: [] }; +} + +function rewriteObjectPattern( + statement: SgNode, + pattern: SgNode, + promisesBinding: string, +): { edits: Edit[]; mappings: BindingMapping[] } { + const shorthandBindings = pattern + .findAll({ + rule: { kind: 'shorthand_property_identifier_pattern' }, + }) + .map((node) => node.text()); + + const aliasedBindings = pattern + .findAll({ + rule: { + kind: 'pair_pattern', + has: { + field: 'key', + kind: 'property_identifier', + }, + }, + }) + .map((pair) => { + const imported = pair.field('key')?.text() ?? ''; + const local = pair.field('value')?.text() ?? imported; + + return { + imported, + local, + text: pair.text(), + }; }); - if (objectPattern) { - let objPatArr = objectPattern - .findAll({ - rule: { kind: 'import_specifier' }, - }) - .map((v) => v.text()); - objPatArr = objPatArr.filter((v) => !patterns.includes(v)); - const promisesImport = objPatArr.find((v) => v.startsWith('promises')); - if (promisesImport) { - if (promisesImport.includes('as')) { - const m = promisesImport.matchAll(/promises as (\w+)/g); - m.forEach((v) => { - promisesImportName = v[1] ?? 'promises'; - }); - } else { - promisesImportName = promisesImport; - } - promisesImportName = `${promisesImportName}.`; - } else { - objPatArr.push('constants'); - } - edits.push(objectPattern.replace(`{ ${objPatArr.join(', ')} }`)); + const kept: string[] = []; + const removed: RemovedBinding[] = []; + let removedShorthandCount = 0; + let removedAliasedCount = 0; + + for (const name of shorthandBindings) { + if (PATTERN_SET.has(name)) { + removedShorthandCount += 1; + removed.push({ + imported: name, + local: name, + }); + continue; } + + kept.push(name); } - for (const _OK of patterns) { - for (const [prefix, replacement] of [ - ['fs.', 'fs.constants.'], - ['', `${promisesImportName ? promisesImportName : ''}constants.`], - ]) { - const patterns = rootNode.findAll({ - rule: { pattern: `${prefix}${_OK}` }, + for (const binding of aliasedBindings) { + if (PATTERN_SET.has(binding.imported)) { + removedAliasedCount += 1; + removed.push({ + imported: binding.imported, + local: binding.local, }); - for (const pattern of patterns) { - edits.push(pattern.replace(`${replacement}${_OK}`)); + continue; + } + + kept.push(binding.text); + } + + return rewriteCollectedBindings({ + statement, + pattern, + promisesBinding, + kept, + removed, + allowSingleBindingOptimization: + removedShorthandCount === 1 && removedAliasedCount === 0, + }); +} + +function rewriteNamedImports( + statement: SgNode, + pattern: SgNode, + promisesBinding: string, +): { edits: Edit[]; mappings: BindingMapping[] } { + const specifiers = pattern.findAll({ + rule: { kind: 'import_specifier' }, + }); + + const kept: string[] = []; + const removed: RemovedBinding[] = []; + + for (const specifier of specifiers) { + const imported = specifier.field('name')?.text() ?? ''; + const local = specifier.field('alias')?.text() ?? imported; + + if (PATTERN_SET.has(imported)) { + removed.push({ + imported, + local, + }); + + continue; + } + + kept.push(specifier.text()); + } + + return rewriteCollectedBindings({ + statement, + pattern, + promisesBinding, + kept, + removed, + allowSingleBindingOptimization: + removed.length === 1 && removed[0].local === removed[0].imported, + }); +} + +function applyNamespaceReplacements( + rootNode: SgNode, + edits: Edit[], + replacements: Map, +): void { + for (const [path, replacement] of replacements) { + const nodes = rootNode.findAll({ rule: { pattern: path } }); + + for (const node of nodes) { + edits.push(node.replace(replacement)); + } + } +} + +function applyLocalReplacements( + rootNode: SgNode, + edits: Edit[], + replacements: Map, +): void { + for (const [local, replacement] of replacements) { + const identifiers = rootNode.findAll({ + rule: { + kind: 'identifier', + regex: `^${escapeRegExp(local)}$`, + }, + }); + + for (const identifier of identifiers) { + if ( + !identifier.inside({ rule: { kind: 'named_imports' } }) || + !identifier.inside({ rule: { kind: 'object_pattern' } }) + ) { + edits.push(identifier.replace(replacement)); } } } +} - return rootNode.commitEdits(edits); +function rewriteCollectedBindings({ + statement, + pattern, + promisesBinding, + kept, + removed, + allowSingleBindingOptimization, +}: { + statement: SgNode; + pattern: SgNode; + promisesBinding: string; + kept: string[]; + removed: RemovedBinding[]; + allowSingleBindingOptimization: boolean; +}): { edits: Edit[]; mappings: BindingMapping[] } { + if (!removed.length) return { edits: [], mappings: [] }; + + const replacementPrefix = promisesBinding + ? `${promisesBinding}.constants` + : 'constants'; + const mappings = removed.map((binding) => ({ + local: binding.local, + replacement: `${replacementPrefix}.${binding.imported}`, + })); + + const shouldAddConstants = !promisesBinding && !kept.includes('constants'); + + if (allowSingleBindingOptimization && removed.length === 1) { + const singleBindingEdit = updateBinding(statement, { + old: removed[0].imported, + new: shouldAddConstants ? 'constants' : undefined, + }).edit; + + if (singleBindingEdit) return { edits: [singleBindingEdit], mappings }; + } + + if (shouldAddConstants) kept.push('constants'); + + return { + edits: [pattern.replace(`{ ${kept.join(', ')} }`)], + mappings, + }; +} + +function escapeRegExp(text: string): string { + return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } diff --git a/recipes/fs-access-mode-constants/tests/expected/aliased-import-bindings.js b/recipes/fs-access-mode-constants/tests/expected/aliased-import-bindings.js new file mode 100644 index 00000000..1551d984 --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/expected/aliased-import-bindings.js @@ -0,0 +1,3 @@ +import { access, constants } from 'node:fs'; + +access('/path/to/file', constants.F_OK | constants.X_OK, callback); diff --git a/recipes/fs-access-mode-constants/tests/expected/aliased-require-bindings.js b/recipes/fs-access-mode-constants/tests/expected/aliased-require-bindings.js new file mode 100644 index 00000000..8be1b289 --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/expected/aliased-require-bindings.js @@ -0,0 +1,3 @@ +const { access, constants } = require('node:fs'); + +access('/path/to/file', constants.F_OK | constants.R_OK, callback); diff --git a/recipes/fs-access-mode-constants/tests/expected/already-constants-import.js b/recipes/fs-access-mode-constants/tests/expected/already-constants-import.js new file mode 100644 index 00000000..cbcd24a1 --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/expected/already-constants-import.js @@ -0,0 +1,3 @@ +import { access, constants } from 'node:fs'; + +access('/path/to/file', constants.F_OK, callback); diff --git a/recipes/fs-access-mode-constants/tests/expected/already-constants-namespace.js b/recipes/fs-access-mode-constants/tests/expected/already-constants-namespace.js new file mode 100644 index 00000000..7af7387c --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/expected/already-constants-namespace.js @@ -0,0 +1,4 @@ +import * as fs from 'node:fs'; + +fs.access('/path/to/file', fs.constants.F_OK, callback); +fs.access('/path/to/file', fs.constants.R_OK | fs.constants.W_OK, callback); diff --git a/recipes/fs-access-mode-constants/tests/expected/already-constants-require.js b/recipes/fs-access-mode-constants/tests/expected/already-constants-require.js new file mode 100644 index 00000000..314cce18 --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/expected/already-constants-require.js @@ -0,0 +1,3 @@ +const fs = require('node:fs'); + +fs.accessSync('/path/to/file', fs.constants.X_OK); diff --git a/recipes/fs-access-mode-constants/tests/expected/file-07.js b/recipes/fs-access-mode-constants/tests/expected/constants-destructured-only.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/expected/file-07.js rename to recipes/fs-access-mode-constants/tests/expected/constants-destructured-only.js diff --git a/recipes/fs-access-mode-constants/tests/expected/destructured-from-constants.js b/recipes/fs-access-mode-constants/tests/expected/destructured-from-constants.js new file mode 100644 index 00000000..330d8a1c --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/expected/destructured-from-constants.js @@ -0,0 +1,4 @@ +const { constants } = require('node:fs'); +const { F_OK, R_OK } = constants; + +console.log(F_OK, R_OK); diff --git a/recipes/fs-access-mode-constants/tests/expected/file-03.js b/recipes/fs-access-mode-constants/tests/expected/destructured-import.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/expected/file-03.js rename to recipes/fs-access-mode-constants/tests/expected/destructured-import.js diff --git a/recipes/fs-access-mode-constants/tests/expected/file-02.js b/recipes/fs-access-mode-constants/tests/expected/destructured-require.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/expected/file-02.js rename to recipes/fs-access-mode-constants/tests/expected/destructured-require.js diff --git a/recipes/fs-access-mode-constants/tests/expected/file-04.js b/recipes/fs-access-mode-constants/tests/expected/mixed-require-namespace.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/expected/file-04.js rename to recipes/fs-access-mode-constants/tests/expected/mixed-require-namespace.js diff --git a/recipes/fs-access-mode-constants/tests/expected/file-01.js b/recipes/fs-access-mode-constants/tests/expected/namespace-import-basic.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/expected/file-01.js rename to recipes/fs-access-mode-constants/tests/expected/namespace-import-basic.js diff --git a/recipes/fs-access-mode-constants/tests/expected/file-06.js b/recipes/fs-access-mode-constants/tests/expected/namespace-multiple-uses.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/expected/file-06.js rename to recipes/fs-access-mode-constants/tests/expected/namespace-multiple-uses.js diff --git a/recipes/fs-access-mode-constants/tests/expected/file-00.js b/recipes/fs-access-mode-constants/tests/expected/namespace-require-basic.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/expected/file-00.js rename to recipes/fs-access-mode-constants/tests/expected/namespace-require-basic.js diff --git a/recipes/fs-access-mode-constants/tests/expected/non-fs-import-f-ok.js b/recipes/fs-access-mode-constants/tests/expected/non-fs-import-f-ok.js new file mode 100644 index 00000000..cbcaa257 --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/expected/non-fs-import-f-ok.js @@ -0,0 +1,3 @@ +import { F_OK } from './local-constants.js'; + +console.log(F_OK); diff --git a/recipes/fs-access-mode-constants/tests/expected/non-fs-require-f-ok.js b/recipes/fs-access-mode-constants/tests/expected/non-fs-require-f-ok.js new file mode 100644 index 00000000..35ba65d6 --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/expected/non-fs-require-f-ok.js @@ -0,0 +1,3 @@ +const { F_OK } = require('./local-constants.cjs'); + +console.log(F_OK); diff --git a/recipes/fs-access-mode-constants/tests/expected/file-05.js b/recipes/fs-access-mode-constants/tests/expected/promises-alias-import.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/expected/file-05.js rename to recipes/fs-access-mode-constants/tests/expected/promises-alias-import.js diff --git a/recipes/fs-access-mode-constants/tests/expected/unrelated-f-ok-identifier.js b/recipes/fs-access-mode-constants/tests/expected/unrelated-f-ok-identifier.js new file mode 100644 index 00000000..e159c8bd --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/expected/unrelated-f-ok-identifier.js @@ -0,0 +1,4 @@ +import { readFileSync } from 'node:fs'; + +const F_OK = 1; +console.log(readFileSync, F_OK); diff --git a/recipes/fs-access-mode-constants/tests/input/aliased-import-bindings.js b/recipes/fs-access-mode-constants/tests/input/aliased-import-bindings.js new file mode 100644 index 00000000..0ab329dd --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/input/aliased-import-bindings.js @@ -0,0 +1,3 @@ +import { access, F_OK as fileExists, X_OK as canExec } from 'node:fs'; + +access('/path/to/file', fileExists | canExec, callback); diff --git a/recipes/fs-access-mode-constants/tests/input/aliased-require-bindings.js b/recipes/fs-access-mode-constants/tests/input/aliased-require-bindings.js new file mode 100644 index 00000000..7080934f --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/input/aliased-require-bindings.js @@ -0,0 +1,3 @@ +const { access, F_OK: fileExists, R_OK: canRead } = require('node:fs'); + +access('/path/to/file', fileExists | canRead, callback); diff --git a/recipes/fs-access-mode-constants/tests/input/already-constants-import.js b/recipes/fs-access-mode-constants/tests/input/already-constants-import.js new file mode 100644 index 00000000..cbcd24a1 --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/input/already-constants-import.js @@ -0,0 +1,3 @@ +import { access, constants } from 'node:fs'; + +access('/path/to/file', constants.F_OK, callback); diff --git a/recipes/fs-access-mode-constants/tests/input/already-constants-namespace.js b/recipes/fs-access-mode-constants/tests/input/already-constants-namespace.js new file mode 100644 index 00000000..7af7387c --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/input/already-constants-namespace.js @@ -0,0 +1,4 @@ +import * as fs from 'node:fs'; + +fs.access('/path/to/file', fs.constants.F_OK, callback); +fs.access('/path/to/file', fs.constants.R_OK | fs.constants.W_OK, callback); diff --git a/recipes/fs-access-mode-constants/tests/input/already-constants-require.js b/recipes/fs-access-mode-constants/tests/input/already-constants-require.js new file mode 100644 index 00000000..314cce18 --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/input/already-constants-require.js @@ -0,0 +1,3 @@ +const fs = require('node:fs'); + +fs.accessSync('/path/to/file', fs.constants.X_OK); diff --git a/recipes/fs-access-mode-constants/tests/input/file-07.js b/recipes/fs-access-mode-constants/tests/input/constants-destructured-only.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/input/file-07.js rename to recipes/fs-access-mode-constants/tests/input/constants-destructured-only.js diff --git a/recipes/fs-access-mode-constants/tests/input/destructured-from-constants.js b/recipes/fs-access-mode-constants/tests/input/destructured-from-constants.js new file mode 100644 index 00000000..330d8a1c --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/input/destructured-from-constants.js @@ -0,0 +1,4 @@ +const { constants } = require('node:fs'); +const { F_OK, R_OK } = constants; + +console.log(F_OK, R_OK); diff --git a/recipes/fs-access-mode-constants/tests/input/file-03.js b/recipes/fs-access-mode-constants/tests/input/destructured-import.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/input/file-03.js rename to recipes/fs-access-mode-constants/tests/input/destructured-import.js diff --git a/recipes/fs-access-mode-constants/tests/input/file-02.js b/recipes/fs-access-mode-constants/tests/input/destructured-require.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/input/file-02.js rename to recipes/fs-access-mode-constants/tests/input/destructured-require.js diff --git a/recipes/fs-access-mode-constants/tests/input/file-04.js b/recipes/fs-access-mode-constants/tests/input/mixed-require-namespace.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/input/file-04.js rename to recipes/fs-access-mode-constants/tests/input/mixed-require-namespace.js diff --git a/recipes/fs-access-mode-constants/tests/input/file-01.js b/recipes/fs-access-mode-constants/tests/input/namespace-import-basic.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/input/file-01.js rename to recipes/fs-access-mode-constants/tests/input/namespace-import-basic.js diff --git a/recipes/fs-access-mode-constants/tests/input/file-06.js b/recipes/fs-access-mode-constants/tests/input/namespace-multiple-uses.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/input/file-06.js rename to recipes/fs-access-mode-constants/tests/input/namespace-multiple-uses.js diff --git a/recipes/fs-access-mode-constants/tests/input/file-00.js b/recipes/fs-access-mode-constants/tests/input/namespace-require-basic.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/input/file-00.js rename to recipes/fs-access-mode-constants/tests/input/namespace-require-basic.js diff --git a/recipes/fs-access-mode-constants/tests/input/non-fs-import-f-ok.js b/recipes/fs-access-mode-constants/tests/input/non-fs-import-f-ok.js new file mode 100644 index 00000000..cbcaa257 --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/input/non-fs-import-f-ok.js @@ -0,0 +1,3 @@ +import { F_OK } from './local-constants.js'; + +console.log(F_OK); diff --git a/recipes/fs-access-mode-constants/tests/input/non-fs-require-f-ok.js b/recipes/fs-access-mode-constants/tests/input/non-fs-require-f-ok.js new file mode 100644 index 00000000..35ba65d6 --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/input/non-fs-require-f-ok.js @@ -0,0 +1,3 @@ +const { F_OK } = require('./local-constants.cjs'); + +console.log(F_OK); diff --git a/recipes/fs-access-mode-constants/tests/input/file-05.js b/recipes/fs-access-mode-constants/tests/input/promises-alias-import.js similarity index 100% rename from recipes/fs-access-mode-constants/tests/input/file-05.js rename to recipes/fs-access-mode-constants/tests/input/promises-alias-import.js diff --git a/recipes/fs-access-mode-constants/tests/input/unrelated-f-ok-identifier.js b/recipes/fs-access-mode-constants/tests/input/unrelated-f-ok-identifier.js new file mode 100644 index 00000000..e159c8bd --- /dev/null +++ b/recipes/fs-access-mode-constants/tests/input/unrelated-f-ok-identifier.js @@ -0,0 +1,4 @@ +import { readFileSync } from 'node:fs'; + +const F_OK = 1; +console.log(readFileSync, F_OK);